From 1c6703356d925ef4c5b47c4c84ecf9990f137000 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Fri, 26 Nov 2021 09:24:34 +0000 Subject: [PATCH] Highlight my vote even if it was made on another device (#7202) --- src/components/views/messages/MPollBody.tsx | 46 +++++++++---------- .../views/messages/MPollBody-test.tsx | 27 +++++++++++ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index b93c27eaa5..69ed8d22d5 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -29,7 +29,7 @@ import { import StyledRadioButton from '../elements/StyledRadioButton'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Relations } from 'matrix-js-sdk/src/models/relations'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; import ErrorDialog from '../dialogs/ErrorDialog'; // TODO: [andyb] Use extensible events library when ready @@ -42,20 +42,16 @@ interface IState { @replaceableComponent("views.messages.MPollBody") export default class MPollBody extends React.Component { + static contextType = MatrixClientContext; + public context!: React.ContextType; + constructor(props: IBodyProps) { super(props); - const pollRelations = this.fetchPollRelations(); - let selected = null; - - const userVotes = collectUserVotes(allVotes(pollRelations), null); - const userId = MatrixClientPeg.get().getUserId(); - const currentVote = userVotes.get(userId); - if (currentVote) { - selected = currentVote.answers[0]; - } - - this.state = { selected, pollRelations }; + this.state = { + selected: null, + pollRelations: this.fetchPollRelations(), + }; this.addListeners(this.state.pollRelations); this.props.mxEvent.on("Event.relationsCreated", this.onPollRelationsCreated); @@ -119,7 +115,8 @@ export default class MPollBody extends React.Component { "rel_type": "m.reference", }, }; - MatrixClientPeg.get().sendEvent( + + this.context.sendEvent( this.props.mxEvent.getRoomId(), POLL_RESPONSE_EVENT_TYPE.name, responseContent, @@ -158,12 +155,13 @@ export default class MPollBody extends React.Component { } /** - * @returns answer-id -> number-of-votes + * @returns userId -> UserVote */ - private collectVotes(): Map { - return countVotes( - collectUserVotes(allVotes(this.state.pollRelations), this.state.selected), - this.props.mxEvent.getContent(), + private collectUserVotes(): Map { + return collectUserVotes( + allVotes(this.state.pollRelations), + this.context.getUserId(), + this.state.selected, ); } @@ -184,15 +182,18 @@ export default class MPollBody extends React.Component { } const pollId = this.props.mxEvent.getId(); - const votes = this.collectVotes(); + const userVotes = this.collectUserVotes(); + const votes = countVotes(userVotes, this.props.mxEvent.getContent()); const totalVotes = this.totalVotes(votes); + const userId = this.context.getUserId(); + const myVote = userVotes.get(userId)?.answers[0]; return

{ pollInfo.question[TEXT_NODE_TYPE] }

{ pollInfo.answers.map((answer: IPollAnswer) => { - const checked = this.state.selected === answer.id; + const checked = myVote === answer.id; const classNames = `mx_MPollBody_option${ checked ? " mx_MPollBody_option_checked": "" }`; @@ -207,7 +208,7 @@ export default class MPollBody extends React.Component {
@@ -275,6 +276,7 @@ export function allVotes(pollRelations: Relations): Array { */ function collectUserVotes( userResponses: Array, + userId: string, selected?: string, ): Map { const userVotes: Map = new Map(); @@ -287,8 +289,6 @@ function collectUserVotes( } if (selected) { - const client = MatrixClientPeg.get(); - const userId = client.getUserId(); userVotes.set(userId, new UserVote(0, userId, [selected])); } diff --git a/test/components/views/messages/MPollBody-test.tsx b/test/components/views/messages/MPollBody-test.tsx index 60d75e7d75..3d2a51f4a5 100644 --- a/test/components/views/messages/MPollBody-test.tsx +++ b/test/components/views/messages/MPollBody-test.tsx @@ -27,6 +27,8 @@ import { IPollAnswer, IPollContent } from "../../../../src/polls/consts"; import { UserVote, allVotes } from "../../../../src/components/views/messages/MPollBody"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; +const CHECKED = "mx_MPollBody_option_checked"; + const _MPollBody = sdk.getComponent("views.messages.MPollBody"); const MPollBody = TestUtils.wrapInMatrixClientContext(_MPollBody); @@ -142,6 +144,25 @@ describe("MPollBody", () => { expect(votesCount(body, "wings")).toBe("1 vote"); expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes"); + + // And my vote is highlighted + expect(voteButton(body, "wings").hasClass(CHECKED)).toBe(true); + expect(voteButton(body, "italian").hasClass(CHECKED)).toBe(false); + }); + + it("highlights my vote even if I did it on another device", () => { + // Given I voted italian + const votes = [ + responseEvent("@me:example.com", "italian"), + responseEvent("@nf:example.com", "wings"), + ]; + const body = newMPollBody(votes); + + // But I didn't click anything locally + + // Then my vote is highlighted, and others are not + expect(voteButton(body, "italian").hasClass(CHECKED)).toBe(true); + expect(voteButton(body, "wings").hasClass(CHECKED)).toBe(false); }); it("ignores extra answers", () => { @@ -380,6 +401,12 @@ function clickRadio(wrapper: ReactWrapper, value: string) { wrapper.find(`StyledRadioButton[value="${value}"]`).simulate("click"); } +function voteButton(wrapper: ReactWrapper, value: string): ReactWrapper { + return wrapper.find( + `div.mx_MPollBody_option`, + ).findWhere(w => w.key() === value); +} + function votesCount(wrapper: ReactWrapper, value: string): string { return wrapper.find( `StyledRadioButton[value="${value}"] .mx_MPollBody_optionVoteCount`,