Merge 397f9b6e5a
into 865c5b0e9c
This commit is contained in:
commit
553549ae4d
13 changed files with 1016 additions and 209 deletions
|
@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { Bot } from "../../pages/bot";
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, test } from "../../element-web-test";
|
||||
import { Bot } from "../../pages/bot";
|
||||
|
||||
test.describe("Polls", () => {
|
||||
type CreatePollOptions = {
|
||||
|
@ -59,6 +59,33 @@ test.describe("Polls", () => {
|
|||
).toContainText(`${votes} vote`);
|
||||
};
|
||||
|
||||
const getPollResultsDialog = (page: Page): Locator => {
|
||||
return page.locator(".mx_PollResultsDialog");
|
||||
};
|
||||
|
||||
const getPollResultsDialogOption = (page: Page, optionText: string): Locator => {
|
||||
return getPollResultsDialog(page).locator(".mx_AnswerEntry").filter({ hasText: optionText });
|
||||
};
|
||||
|
||||
const expectDetailedPollOptionVoteCount = async (
|
||||
page: Page,
|
||||
pollId: string,
|
||||
optionText: string,
|
||||
votes: number,
|
||||
optLocator?: Locator,
|
||||
): Promise<void> => {
|
||||
await expect(
|
||||
getPollResultsDialogOption(page, optionText)
|
||||
.locator(".mx_AnswerEntry_Header")
|
||||
.locator(".mx_AnswerEntry_Header_answerName"),
|
||||
).toContainText(optionText);
|
||||
await expect(
|
||||
getPollResultsDialogOption(page, optionText)
|
||||
.locator(".mx_AnswerEntry_Header")
|
||||
.locator(".mx_AnswerEntry_Header_voteCount"),
|
||||
).toContainText(`${votes} vote`);
|
||||
};
|
||||
|
||||
const botVoteForOption = async (
|
||||
page: Page,
|
||||
bot: Bot,
|
||||
|
@ -219,6 +246,70 @@ test.describe("Polls", () => {
|
|||
await expect(page.locator(".mx_ErrorDialog")).toBeAttached();
|
||||
});
|
||||
|
||||
test("should allow to view detailed results after voting", async ({ page, app, bot, user }) => {
|
||||
const roomId: string = await app.client.createRoom({});
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await page.goto("/#/room/" + roomId);
|
||||
// wait until Bob joined
|
||||
await expect(page.getByText("BotBob joined the room")).toBeAttached();
|
||||
|
||||
const locator = await app.openMessageComposerOptions();
|
||||
await locator.getByRole("menuitem", { name: "Poll" }).click();
|
||||
|
||||
// Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688
|
||||
//cy.get(".mx_CompoundDialog").percySnapshotElement("Polls Composer");
|
||||
|
||||
const pollParams = {
|
||||
title: "Does the polls feature work?",
|
||||
options: ["Yes", "No", "Maybe?"],
|
||||
};
|
||||
await createPoll(page, pollParams);
|
||||
|
||||
// Wait for message to send, get its ID and save as @pollId
|
||||
const pollId = await page
|
||||
.locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]")
|
||||
.filter({ hasText: pollParams.title })
|
||||
.getAttribute("data-scroll-tokens");
|
||||
await expect(getPollTile(page, pollId)).toMatchScreenshot("Polls_Timeline_tile_no_votes.png", {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
|
||||
// Bot votes 'Maybe' in the poll
|
||||
await botVoteForOption(page, bot, roomId, pollId, pollParams.options[2]);
|
||||
|
||||
// no votes shown until I vote, check bots vote has arrived
|
||||
await expect(
|
||||
page.locator(".mx_MPollBody_totalVotes").getByText("1 vote cast. Vote to see the results"),
|
||||
).toBeAttached();
|
||||
|
||||
// vote 'Maybe'
|
||||
await getPollOption(page, pollId, pollParams.options[2]).click();
|
||||
// both me and bot have voted Maybe
|
||||
await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 2);
|
||||
|
||||
// click the 'vote to see results' message
|
||||
await page
|
||||
.locator(".mx_MPollBody_totalVotes")
|
||||
.getByText("Based on 2 votes. Click here to see full results")
|
||||
.click();
|
||||
|
||||
// expect the detailed results to be shown
|
||||
await expect(getPollResultsDialog(page)).toBeAttached();
|
||||
|
||||
// expect results to be correctly shown
|
||||
await expectDetailedPollOptionVoteCount(page, pollId, pollParams.options[2], 2);
|
||||
const voterEntries = getPollResultsDialogOption(page, pollParams.options[2]).locator(".mx_VoterEntry");
|
||||
expect((await voterEntries.all()).length).toBe(2);
|
||||
expect(voterEntries.filter({ hasText: bot.credentials.displayName })).not.toBeNull();
|
||||
expect(voterEntries.filter({ hasText: user.displayName })).not.toBeNull();
|
||||
|
||||
// close the dialog
|
||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Close" }).click();
|
||||
|
||||
// expect the dialog to be closed
|
||||
await expect(getPollResultsDialog(page)).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("should be displayed correctly in thread panel", async ({ page, app, user, bot, homeserver }) => {
|
||||
const botCharlie = new Bot(page, homeserver, { displayName: "BotCharlie" });
|
||||
await botCharlie.prepareClient();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
@import "./components/views/dialogs/polls/_PollDetailHeader.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollResultsDialog.pcss";
|
||||
@import "./components/views/elements/_AppPermission.pcss";
|
||||
@import "./components/views/elements/_AppWarning.pcss";
|
||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
.mx_AnswerEntry:not(:last-child) {
|
||||
margin-bottom: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_AnswerEntry_Header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_AnswerEntry_Header_answerName {
|
||||
font-weight: bolder;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mx_VoterEntry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: $spacing-16;
|
||||
}
|
||||
|
||||
.mx_VoterEntry_AvatarWrapper {
|
||||
margin-right: $spacing-8;
|
||||
}
|
|
@ -35,6 +35,14 @@ Please see LICENSE files in the repository root for full details.
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mx_PollOption_votesWrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_PollOption_facePile {
|
||||
margin-right: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_PollOption_optionVoteCount {
|
||||
color: $secondary-content;
|
||||
font-size: $font-12px;
|
||||
|
|
70
src/components/views/dialogs/PollResultsDialog.tsx
Normal file
70
src/components/views/dialogs/PollResultsDialog.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright 2024 Tim Vahlbrock
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { PollAnswerSubevent, PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import { UserVote } from "../messages/MPollBody";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface IProps {
|
||||
pollEvent: PollStartEvent;
|
||||
votes: Map<string, UserVote[]>;
|
||||
members: RoomMember[];
|
||||
}
|
||||
|
||||
export default function PollResultsDialog(props: IProps): JSX.Element {
|
||||
return (
|
||||
<BaseDialog
|
||||
title={props.pollEvent.question.text}
|
||||
onFinished={() => Modal.closeCurrentModal()}
|
||||
className="mx_PollResultsDialog"
|
||||
>
|
||||
{props.pollEvent.answers.map((answer) => {
|
||||
const votes = props.votes.get(answer.id) || [];
|
||||
if (votes.length === 0) return;
|
||||
|
||||
return <AnswerEntry key={answer.id} answer={answer} members={props.members} votes={votes} />;
|
||||
})}
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
||||
function AnswerEntry(props: { answer: PollAnswerSubevent; members: RoomMember[]; votes: UserVote[] }): JSX.Element {
|
||||
const { answer, members, votes } = props;
|
||||
return (
|
||||
<div key={answer.id} className="mx_AnswerEntry">
|
||||
<div className="mx_AnswerEntry_Header">
|
||||
<span className="mx_AnswerEntry_Header_answerName">{answer.text}</span>
|
||||
<span className="mx_AnswerEntry_Header_voteCount">
|
||||
{_t("poll|result_dialog|count_of_votes", { count: votes.length })}
|
||||
</span>
|
||||
</div>
|
||||
{votes.length === 0 && <div>No one voted for this.</div>}
|
||||
{votes.map((vote) => {
|
||||
const member = members.find((m) => m.userId === vote.sender);
|
||||
if (member) return <VoterEntry key={vote.sender} vote={vote} member={member} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VoterEntry(props: { vote: UserVote; member: RoomMember }): JSX.Element {
|
||||
const { vote, member } = props;
|
||||
return (
|
||||
<div key={vote.sender} className="mx_VoterEntry">
|
||||
<div className="mx_VoterEntry_AvatarWrapper">
|
||||
<MemberAvatar member={member} size="36px" aria-hidden="true" />
|
||||
</div>
|
||||
{member.name}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -6,34 +6,35 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
|
||||
import { PollAnswerSubevent, PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import {
|
||||
MatrixEvent,
|
||||
MatrixClient,
|
||||
Relations,
|
||||
Poll,
|
||||
PollEvent,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_RESPONSE,
|
||||
M_POLL_START,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
Poll,
|
||||
PollEvent,
|
||||
Relations,
|
||||
TimelineEvents,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
|
||||
import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||
import PollCreateDialog from "../elements/PollCreateDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import Modal from "../../../Modal";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import PollResultsDialog from "../dialogs/PollResultsDialog";
|
||||
import PollCreateDialog from "../elements/PollCreateDialog";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { PollOption } from "../polls/PollOption";
|
||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
||||
interface IState {
|
||||
poll?: Poll;
|
||||
|
@ -81,12 +82,12 @@ export function findTopAnswer(pollEvent: MatrixEvent, voteRelations: Relations):
|
|||
|
||||
const userVotes: Map<string, UserVote> = collectUserVotes(allVotes(voteRelations));
|
||||
|
||||
const votes: Map<string, number> = countVotes(userVotes, poll);
|
||||
const highestScore: number = Math.max(...votes.values());
|
||||
const votes: Map<string, UserVote[]> = countVotes(userVotes, poll);
|
||||
const highestScore: number = Math.max(...Array.from(votes.values()).map((votes) => votes.length));
|
||||
|
||||
const bestAnswerIds: string[] = [];
|
||||
for (const [answerId, score] of votes) {
|
||||
if (score == highestScore) {
|
||||
for (const [answerId, answerVotes] of votes) {
|
||||
if (answerVotes.length == highestScore) {
|
||||
bestAnswerIds.push(answerId);
|
||||
}
|
||||
}
|
||||
|
@ -273,10 +274,10 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
this.setState({ selected: newSelected });
|
||||
}
|
||||
|
||||
private totalVotes(collectedVotes: Map<string, number>): number {
|
||||
private totalVotes(collectedVotes: Map<string, UserVote[]>): number {
|
||||
let sum = 0;
|
||||
for (const v of collectedVotes.values()) {
|
||||
sum += v;
|
||||
sum += v.length;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
@ -294,7 +295,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
const userVotes = this.collectUserVotes();
|
||||
const votes = countVotes(userVotes, pollEvent);
|
||||
const totalVotes = this.totalVotes(votes);
|
||||
const winCount = Math.max(...votes.values());
|
||||
const winCount = Math.max(...Array.from(votes.values()).map((votes) => votes.length));
|
||||
const userId = this.context.getSafeUserId();
|
||||
const myVote = userVotes?.get(userId)?.answers[0];
|
||||
const disclosed = M_POLL_KIND_DISCLOSED.matches(pollEvent.kind.name);
|
||||
|
@ -324,6 +325,16 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
<span className="mx_MPollBody_edited"> ({_t("common|edited")})</span>
|
||||
) : null;
|
||||
|
||||
const showDetailedVotes = (): void => {
|
||||
if (!showResults) return;
|
||||
|
||||
Modal.createDialog(PollResultsDialog, {
|
||||
pollEvent,
|
||||
votes,
|
||||
members: this.context.getRoom(this.props.mxEvent.getRoomId())?.getJoinedMembers() ?? [],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx_MPollBody">
|
||||
<h2 data-testid="pollQuestion">
|
||||
|
@ -335,7 +346,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
let answerVotes = 0;
|
||||
|
||||
if (showResults) {
|
||||
answerVotes = votes.get(answer.id) ?? 0;
|
||||
answerVotes = votes.get(answer.id)?.length ?? 0;
|
||||
}
|
||||
|
||||
const checked =
|
||||
|
@ -348,7 +359,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
answer={answer}
|
||||
isChecked={checked}
|
||||
isEnded={poll.isEnded}
|
||||
voteCount={answerVotes}
|
||||
votes={votes.get(answer.id) ?? []}
|
||||
totalVoteCount={totalVotes}
|
||||
displayVoteCount={showResults}
|
||||
onOptionSelected={this.selectOption.bind(this)}
|
||||
|
@ -356,8 +367,10 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<div data-testid="totalVotes" className="mx_MPollBody_totalVotes">
|
||||
{totalText}
|
||||
<div className="mx_MPollBody_totalVotes">
|
||||
<span data-testid="totalVotes" onClick={() => showDetailedVotes()}>
|
||||
{totalText}
|
||||
</span>
|
||||
{isFetchingResponses && <Spinner w={16} h={16} />}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -395,7 +408,7 @@ export function allVotes(voteRelations: Relations): Array<UserVote> {
|
|||
/**
|
||||
* Figure out the correct vote for each user.
|
||||
* @param userResponses current vote responses in the poll
|
||||
* @param {string?} userId The userId for which the `selected` option will apply to.
|
||||
* @param {string?} user The userId for which the `selected` option will apply to.
|
||||
* Should be set to the current user ID.
|
||||
* @param {string?} selected Local echo selected option for the userId
|
||||
* @returns a Map of user ID to their vote info
|
||||
|
@ -421,19 +434,17 @@ export function collectUserVotes(
|
|||
return userVotes;
|
||||
}
|
||||
|
||||
export function countVotes(userVotes: Map<string, UserVote>, pollStart: PollStartEvent): Map<string, number> {
|
||||
const collected = new Map<string, number>();
|
||||
export function countVotes(userVotes: Map<string, UserVote>, pollStart: PollStartEvent): Map<string, UserVote[]> {
|
||||
const collected = new Map<string, UserVote[]>();
|
||||
|
||||
for (const response of userVotes.values()) {
|
||||
const tempResponse = PollResponseEvent.from(response.answers, "$irrelevant");
|
||||
tempResponse.validateAgainst(pollStart);
|
||||
if (!tempResponse.spoiled) {
|
||||
for (const answerId of tempResponse.answerIds) {
|
||||
if (collected.has(answerId)) {
|
||||
collected.set(answerId, collected.get(answerId)! + 1);
|
||||
} else {
|
||||
collected.set(answerId, 1);
|
||||
}
|
||||
const previousVotes = collected.get(answerId) ?? [];
|
||||
previousVotes.push(response);
|
||||
collected.set(answerId, previousVotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,28 +6,47 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import React, { ReactNode, useContext } from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Icon as TrophyIcon } from "../../../../res/img/element-icons/trophy.svg";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import FacePile from "../elements/FacePile";
|
||||
import StyledRadioButton from "../elements/StyledRadioButton";
|
||||
import { UserVote } from "../messages/MPollBody";
|
||||
|
||||
const MAXIMUM_MEMBERS_FOR_FACE_PILE = 5;
|
||||
|
||||
type PollOptionContentProps = {
|
||||
answer: PollAnswerSubevent;
|
||||
voteCount: number;
|
||||
votes: UserVote[];
|
||||
displayVoteCount?: boolean;
|
||||
isWinner?: boolean;
|
||||
};
|
||||
const PollOptionContent: React.FC<PollOptionContentProps> = ({ isWinner, answer, voteCount, displayVoteCount }) => {
|
||||
const votesText = displayVoteCount ? _t("timeline|m.poll|count_of_votes", { count: voteCount }) : "";
|
||||
const PollOptionContent: React.FC<PollOptionContentProps> = ({ isWinner, answer, votes, displayVoteCount }) => {
|
||||
const votesText = displayVoteCount ? _t("timeline|m.poll|count_of_votes", { count: votes.length }) : "";
|
||||
const room = useContext(RoomContext).room;
|
||||
const members = room?.getJoinedMembers() || [];
|
||||
|
||||
return (
|
||||
<div className="mx_PollOption_content">
|
||||
<div className="mx_PollOption_optionText">{answer.text}</div>
|
||||
<div className="mx_PollOption_optionVoteCount">
|
||||
{isWinner && <TrophyIcon className="mx_PollOption_winnerIcon" />}
|
||||
{votesText}
|
||||
<div className="mx_PollOption_votesWrapper">
|
||||
{displayVoteCount && members.length <= MAXIMUM_MEMBERS_FOR_FACE_PILE && (
|
||||
<div className="mx_PollOption_facePile">
|
||||
<FacePile
|
||||
members={members.filter((m) => votes.some((v) => v.sender === m.userId))}
|
||||
size="24px"
|
||||
overflow={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span className="mx_PollOption_optionVoteCount">
|
||||
{isWinner && <TrophyIcon className="mx_PollOption_winnerIcon" />}
|
||||
{votesText}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -42,7 +61,7 @@ interface PollOptionProps extends PollOptionContentProps {
|
|||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const EndedPollOption: React.FC<Omit<PollOptionProps, "voteCount" | "totalVoteCount">> = ({
|
||||
const EndedPollOption: React.FC<Omit<PollOptionProps, "votes" | "totalVoteCount">> = ({
|
||||
isChecked,
|
||||
children,
|
||||
answer,
|
||||
|
@ -57,7 +76,7 @@ const EndedPollOption: React.FC<Omit<PollOptionProps, "voteCount" | "totalVoteCo
|
|||
</div>
|
||||
);
|
||||
|
||||
const ActivePollOption: React.FC<Omit<PollOptionProps, "voteCount" | "totalVoteCount">> = ({
|
||||
const ActivePollOption: React.FC<Omit<PollOptionProps, "votes" | "totalVoteCount">> = ({
|
||||
pollId,
|
||||
isChecked,
|
||||
children,
|
||||
|
@ -78,7 +97,7 @@ const ActivePollOption: React.FC<Omit<PollOptionProps, "voteCount" | "totalVoteC
|
|||
export const PollOption: React.FC<PollOptionProps> = ({
|
||||
pollId,
|
||||
answer,
|
||||
voteCount,
|
||||
votes: voteCount,
|
||||
totalVoteCount,
|
||||
displayVoteCount,
|
||||
isEnded,
|
||||
|
@ -91,7 +110,7 @@ export const PollOption: React.FC<PollOptionProps> = ({
|
|||
mx_PollOption_ended: isEnded,
|
||||
});
|
||||
const isWinner = isEnded && isChecked;
|
||||
const answerPercent = totalVoteCount === 0 ? 0 : Math.round((100.0 * voteCount) / totalVoteCount);
|
||||
const answerPercent = totalVoteCount === 0 ? 0 : Math.round((100.0 * voteCount.length) / totalVoteCount);
|
||||
const PollOptionWrapper = isEnded ? EndedPollOption : ActivePollOption;
|
||||
return (
|
||||
<div data-testid={`pollOption-${answer.id}`} className={cls} onClick={() => onOptionSelected?.(answer.id)}>
|
||||
|
@ -104,7 +123,7 @@ export const PollOption: React.FC<PollOptionProps> = ({
|
|||
<PollOptionContent
|
||||
isWinner={isWinner}
|
||||
answer={answer}
|
||||
voteCount={voteCount}
|
||||
votes={voteCount}
|
||||
displayVoteCount={displayVoteCount}
|
||||
/>
|
||||
</PollOptionWrapper>
|
||||
|
|
|
@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
import { PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { MatrixEvent, Poll, PollEvent, Relations } from "matrix-js-sdk/src/matrix";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { Icon as PollIcon } from "../../../../../res/img/element-icons/room/composer/poll.svg";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { formatLocalDateShort } from "../../../../DateUtils";
|
||||
import { allVotes, collectUserVotes, countVotes } from "../../messages/MPollBody";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { allVotes, collectUserVotes, countVotes, UserVote } from "../../messages/MPollBody";
|
||||
import { PollOption } from "../../polls/PollOption";
|
||||
import { Caption } from "../../typography/Caption";
|
||||
|
||||
|
@ -27,23 +27,23 @@ interface Props {
|
|||
type EndedPollState = {
|
||||
winningAnswers: {
|
||||
answer: PollAnswerSubevent;
|
||||
voteCount: number;
|
||||
votes: UserVote[];
|
||||
}[];
|
||||
totalVoteCount: number;
|
||||
};
|
||||
const getWinningAnswers = (poll: Poll, responseRelations: Relations): EndedPollState => {
|
||||
const userVotes = collectUserVotes(allVotes(responseRelations));
|
||||
const votes = countVotes(userVotes, poll.pollEvent);
|
||||
const totalVoteCount = [...votes.values()].reduce((sum, vote) => sum + vote, 0);
|
||||
const winCount = Math.max(...votes.values());
|
||||
const totalVoteCount = [...votes.values()].reduce((sum, vote) => sum + vote.length, 0);
|
||||
const winCount = Math.max(...Array.from(votes.values()).map((v) => v.length));
|
||||
|
||||
return {
|
||||
totalVoteCount,
|
||||
winningAnswers: poll.pollEvent.answers
|
||||
.filter((answer) => votes.get(answer.id) === winCount)
|
||||
.filter((answer) => votes.get(answer.id)?.length === winCount)
|
||||
.map((answer) => ({
|
||||
answer,
|
||||
voteCount: votes.get(answer.id) || 0,
|
||||
votes: votes.get(answer.id) || [],
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
@ -100,11 +100,11 @@ export const PollListItemEnded: React.FC<Props> = ({ event, poll, onClick }) =>
|
|||
</div>
|
||||
{!!winningAnswers?.length && (
|
||||
<div className="mx_PollListItemEnded_answers">
|
||||
{winningAnswers?.map(({ answer, voteCount }) => (
|
||||
{winningAnswers?.map(({ answer, votes }) => (
|
||||
<PollOption
|
||||
key={answer.id}
|
||||
answer={answer}
|
||||
voteCount={voteCount}
|
||||
votes={votes}
|
||||
totalVoteCount={totalVoteCount!}
|
||||
pollId={poll.pollId}
|
||||
displayVoteCount
|
||||
|
|
|
@ -1715,6 +1715,12 @@
|
|||
"options_heading": "Create options",
|
||||
"options_label": "Option %(number)s",
|
||||
"options_placeholder": "Write an option",
|
||||
"result_dialog": {
|
||||
"count_of_votes": {
|
||||
"one": "%(count)s vote",
|
||||
"other": "%(count)s votes"
|
||||
}
|
||||
},
|
||||
"topic_heading": "What is your poll question or topic?",
|
||||
"topic_label": "Question or topic",
|
||||
"topic_placeholder": "Write something…",
|
||||
|
@ -1724,8 +1730,8 @@
|
|||
"other": "%(count)s votes cast. Vote to see the results"
|
||||
},
|
||||
"total_n_votes_voted": {
|
||||
"one": "Based on %(count)s vote",
|
||||
"other": "Based on %(count)s votes"
|
||||
"one": "Based on %(count)s vote. Click here to see full results",
|
||||
"other": "Based on %(count)s votes. Click here to see full results"
|
||||
},
|
||||
"total_no_votes": "No votes cast",
|
||||
"total_not_ended": "Results will be visible when the poll is ended",
|
||||
|
@ -1876,8 +1882,8 @@
|
|||
"other": "There are no past polls for the past %(count)s days. Load more polls to view polls for previous months"
|
||||
},
|
||||
"final_result": {
|
||||
"one": "Final result based on %(count)s vote",
|
||||
"other": "Final result based on %(count)s votes"
|
||||
"one": "Final result based on %(count)s vote. Click here to see full results",
|
||||
"other": "Final result based on %(count)s votes. Click here to see full results"
|
||||
},
|
||||
"load_more": "Load more polls",
|
||||
"loading": "Loading polls",
|
||||
|
|
|
@ -6,26 +6,32 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import { act, fireEvent, render, RenderResult, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
Relations,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_KIND_UNDISCLOSED,
|
||||
M_POLL_RESPONSE,
|
||||
M_POLL_START,
|
||||
PollStartEventContent,
|
||||
PollAnswer,
|
||||
M_TEXT,
|
||||
MatrixEvent,
|
||||
PollAnswer,
|
||||
PollStartEventContent,
|
||||
Relations,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import PollResultsDialog from "../../../../../src/components/views/dialogs/PollResultsDialog";
|
||||
import { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
|
||||
import MPollBody, {
|
||||
allVotes,
|
||||
findTopAnswer,
|
||||
isPollEnded,
|
||||
} from "../../../../../src/components/views/messages/MPollBody";
|
||||
import { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import * as languageHandler from "../../../../../src/languageHandler";
|
||||
import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import {
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
|
@ -33,10 +39,6 @@ import {
|
|||
mockClientMethodsUser,
|
||||
setupRoomWithPollEvents,
|
||||
} from "../../../../test-utils";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import * as languageHandler from "../../../../../src/languageHandler";
|
||||
|
||||
const CHECKED = "mx_PollOption_checked";
|
||||
const userId = "@me:example.com";
|
||||
|
@ -99,7 +101,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 4 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores end poll events from unauthorised users", async () => {
|
||||
|
@ -118,7 +122,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 4 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("hides scores if I have not voted", async () => {
|
||||
|
@ -159,7 +165,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 2 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses my local vote", async () => {
|
||||
|
@ -180,7 +188,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 4 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("overrides my other votes with my local vote", async () => {
|
||||
|
@ -202,7 +212,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 2 votes. Click here to see full results",
|
||||
);
|
||||
|
||||
// And my vote is highlighted
|
||||
expect(voteButton(renderResult, "wings").className.includes(CHECKED)).toBe(true);
|
||||
|
@ -234,7 +246,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 1 vote. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("doesn't cancel my local vote if someone else votes", async () => {
|
||||
|
@ -266,7 +280,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 2 votes. Click here to see full results",
|
||||
);
|
||||
|
||||
// And my vote is highlighted
|
||||
expect(voteButton(renderResult, "pizza").className.includes(CHECKED)).toBe(true);
|
||||
|
@ -293,7 +309,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 2 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows un-voting by passing an empty vote", async () => {
|
||||
|
@ -307,7 +325,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 1 vote. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows re-voting after un-voting", async () => {
|
||||
|
@ -322,7 +342,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("2 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 2 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("treats any invalid answer as a spoiled ballot", async () => {
|
||||
|
@ -340,7 +362,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 0 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows re-voting after a spoiled ballot", async () => {
|
||||
|
@ -357,7 +381,9 @@ describe("MPollBody", () => {
|
|||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Based on 1 vote. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders nothing if poll has no answers", async () => {
|
||||
|
@ -425,7 +451,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 5 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("sends a vote event when I choose an option", async () => {
|
||||
|
@ -526,7 +554,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 2 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("counts a single vote as normal if the poll is ended", async () => {
|
||||
|
@ -537,7 +567,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 1 vote. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows ended vote counts of different numbers", async () => {
|
||||
|
@ -557,7 +589,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 5 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after poll ended", async () => {
|
||||
|
@ -577,7 +611,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 5 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("counts votes that arrived after an unauthorised poll end event", async () => {
|
||||
|
@ -601,7 +637,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 5 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -629,7 +667,9 @@ describe("MPollBody", () => {
|
|||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe(
|
||||
"Final result based on 5 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("highlights the winning vote in an ended poll", async () => {
|
||||
|
@ -865,6 +905,21 @@ describe("MPollBody", () => {
|
|||
const { container } = await newMPollBody(votes, ends, undefined, false);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("opens the full results dialog when the total votes link is clicked", async () => {
|
||||
const votes = [
|
||||
responseEvent("@ed:example.com", "pizza", 12),
|
||||
responseEvent("@rf:example.com", "pizza", 12),
|
||||
responseEvent("@th:example.com", "wings", 13),
|
||||
];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
const createDialogSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
fireEvent.click(renderResult.getByTestId("totalVotes"));
|
||||
|
||||
expect(createDialogSpy).toHaveBeenCalledWith(PollResultsDialog, expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
function newVoteRelations(relationEvents: Array<MatrixEvent>): Relations {
|
||||
|
|
|
@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { EventTimeline, M_TEXT, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
|
||||
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 { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import {
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
|
@ -133,7 +133,9 @@ describe("<MPollEndBody />", () => {
|
|||
|
||||
// quick check for poll tile
|
||||
expect(getByTestId("pollQuestion").innerHTML).toEqual("Question?");
|
||||
expect(getByTestId("totalVotes").innerHTML).toEqual("Final result based on 0 votes");
|
||||
expect(getByTestId("totalVotes").innerHTML).toEqual(
|
||||
"Final result based on 0 votes. Click here to see full results",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not render a poll tile when end event is invalid", async () => {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -46,9 +46,26 @@ exports[`<MPollEndBody /> when poll start event exists in current timeline rende
|
|||
Socks
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
class="mx_PollOption_votesWrapper"
|
||||
>
|
||||
0 votes
|
||||
<div
|
||||
class="mx_PollOption_facePile"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_stacked-avatars_mcap2_111"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,9 +95,26 @@ exports[`<MPollEndBody /> when poll start event exists in current timeline rende
|
|||
Shoes
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
class="mx_PollOption_votesWrapper"
|
||||
>
|
||||
0 votes
|
||||
<div
|
||||
class="mx_PollOption_facePile"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_stacked-avatars_mcap2_111"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,9 +130,12 @@ exports[`<MPollEndBody /> when poll start event exists in current timeline rende
|
|||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_totalVotes"
|
||||
data-testid="totalVotes"
|
||||
>
|
||||
Final result based on 0 votes
|
||||
<span
|
||||
data-testid="totalVotes"
|
||||
>
|
||||
Final result based on 0 votes. Click here to see full results
|
||||
</span>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue