Hide votes in a poll until you voted (#7269)

This commit is contained in:
Andy Balaam 2021-12-03 10:26:28 +00:00 committed by GitHub
parent 390dde055a
commit 494af883f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 434 additions and 39 deletions

View file

@ -215,6 +215,19 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const totalVotes = this.totalVotes(votes); const totalVotes = this.totalVotes(votes);
const userId = this.context.getUserId(); const userId = this.context.getUserId();
const myVote = userVotes.get(userId)?.answers[0]; const myVote = userVotes.get(userId)?.answers[0];
let totalText: string;
if (myVote === undefined) {
if (totalVotes === 0) {
totalText = _t("No votes cast");
} else {
totalText = _t(
"%(count)s votes cast. Vote to see the results",
{ count: totalVotes },
);
}
} else {
totalText = _t( "Based on %(count)s votes", { count: totalVotes } );
}
return <div className="mx_MPollBody"> return <div className="mx_MPollBody">
<h2>{ pollInfo.question[TEXT_NODE_TYPE] }</h2> <h2>{ pollInfo.question[TEXT_NODE_TYPE] }</h2>
@ -225,7 +238,12 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const classNames = `mx_MPollBody_option${ const classNames = `mx_MPollBody_option${
checked ? " mx_MPollBody_option_checked": "" checked ? " mx_MPollBody_option_checked": ""
}`; }`;
const answerVotes = votes.get(answer.id) ?? 0; let answerVotes = 0;
let votesText = "";
if (myVote !== undefined) { // Votes hidden if I didn't vote
answerVotes = votes.get(answer.id) ?? 0;
votesText = _t("%(count)s votes", { count: answerVotes });
}
const answerPercent = Math.round( const answerPercent = Math.round(
100.0 * answerVotes / totalVotes); 100.0 * answerVotes / totalVotes);
return <div return <div
@ -244,7 +262,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
{ answer[TEXT_NODE_TYPE] } { answer[TEXT_NODE_TYPE] }
</div> </div>
<div className="mx_MPollBody_optionVoteCount"> <div className="mx_MPollBody_optionVoteCount">
{ _t("%(count)s votes", { count: answerVotes }) } { votesText }
</div> </div>
</div> </div>
</StyledRadioButton> </StyledRadioButton>
@ -259,7 +277,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
} }
</div> </div>
<div className="mx_MPollBody_totalVotes"> <div className="mx_MPollBody_totalVotes">
{ _t( "Based on %(count)s votes", { count: totalVotes } ) } { totalText }
</div> </div>
</div>; </div>;
} }

View file

@ -2065,10 +2065,13 @@
"You sent a verification request": "You sent a verification request", "You sent a verification request": "You sent a verification request",
"Vote not registered": "Vote not registered", "Vote not registered": "Vote not registered",
"Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.", "Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.",
"%(count)s votes|other": "%(count)s votes", "No votes cast": "No votes cast",
"%(count)s votes|one": "%(count)s vote", "%(count)s votes cast. Vote to see the results|other": "%(count)s votes cast. Vote to see the results",
"%(count)s votes cast. Vote to see the results|one": "%(count)s vote cast. Vote to see the results",
"Based on %(count)s votes|other": "Based on %(count)s votes", "Based on %(count)s votes|other": "Based on %(count)s votes",
"Based on %(count)s votes|one": "Based on %(count)s vote", "Based on %(count)s votes|one": "Based on %(count)s vote",
"%(count)s votes|other": "%(count)s votes",
"%(count)s votes|one": "%(count)s vote",
"Error decrypting video": "Error decrypting video", "Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message", "Error processing voice message": "Error processing voice message",
"Add reaction": "Add reaction", "Add reaction": "Add reaction",

View file

@ -63,19 +63,19 @@ describe("MPollBody", () => {
]); ]);
}); });
it("finds no votes if none were made", () => { it("renders no votes if none were made", () => {
const votes = []; const votes = [];
const body = newMPollBody(votes); const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("0 votes"); expect(votesCount(body, "pizza")).toBe("");
expect(votesCount(body, "poutine")).toBe("0 votes"); expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("0 votes"); expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("0 votes"); expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 0 votes"); expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("No votes cast");
}); });
it("finds votes from multiple people", () => { it("finds votes from multiple people", () => {
const votes = [ const votes = [
responseEvent("@andyb:example.com", "pizza"), responseEvent("@me:example.com", "pizza"),
responseEvent("@bellc:example.com", "pizza"), responseEvent("@bellc:example.com", "pizza"),
responseEvent("@catrd:example.com", "poutine"), responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"), responseEvent("@dune2:example.com", "wings"),
@ -88,10 +88,39 @@ describe("MPollBody", () => {
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes"); expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes");
}); });
it("hides scores if I have not voted", () => {
const votes = [
responseEvent("@alice:example.com", "pizza"),
responseEvent("@bellc:example.com", "pizza"),
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("");
expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"4 votes cast. Vote to see the results");
});
it("hides a single vote if I have not voted", () => {
const votes = [
responseEvent("@alice:example.com", "pizza"),
];
const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("");
expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"1 vote cast. Vote to see the results");
});
it("takes someone's most recent vote if they voted several times", () => { it("takes someone's most recent vote if they voted several times", () => {
const votes = [ const votes = [
responseEvent("@fiona:example.com", "pizza", 12), responseEvent("@me:example.com", "pizza", 12),
responseEvent("@fiona:example.com", "wings", 20), // latest fiona responseEvent("@me:example.com", "wings", 20), // latest me
responseEvent("@qbert:example.com", "pizza", 14), responseEvent("@qbert:example.com", "pizza", 14),
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
responseEvent("@qbert:example.com", "wings", 15), responseEvent("@qbert:example.com", "wings", 15),
@ -219,7 +248,7 @@ describe("MPollBody", () => {
// When cb votes for 2 things, we consider the first only // When cb votes for 2 things, we consider the first only
const votes = [ const votes = [
responseEvent("@cb:example.com", ["pizza", "wings"]), responseEvent("@cb:example.com", ["pizza", "wings"]),
responseEvent("@da:example.com", "wings"), responseEvent("@me:example.com", "wings"),
]; ];
const body = newMPollBody(votes); const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("1 vote"); expect(votesCount(body, "pizza")).toBe("1 vote");
@ -233,7 +262,7 @@ describe("MPollBody", () => {
const votes = [ const votes = [
responseEvent("@nc:example.com", "pizza", 12), responseEvent("@nc:example.com", "pizza", 12),
responseEvent("@nc:example.com", [], 13), responseEvent("@nc:example.com", [], 13),
responseEvent("@md:example.com", "italian"), responseEvent("@me:example.com", "italian"),
]; ];
const body = newMPollBody(votes); const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("0 votes"); expect(votesCount(body, "pizza")).toBe("0 votes");
@ -248,7 +277,7 @@ describe("MPollBody", () => {
responseEvent("@op:example.com", "pizza", 12), responseEvent("@op:example.com", "pizza", 12),
responseEvent("@op:example.com", [], 13), responseEvent("@op:example.com", [], 13),
responseEvent("@op:example.com", "italian", 14), responseEvent("@op:example.com", "italian", 14),
responseEvent("@qr:example.com", "italian"), responseEvent("@me:example.com", "italian"),
]; ];
const body = newMPollBody(votes); const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("0 votes"); expect(votesCount(body, "pizza")).toBe("0 votes");
@ -263,8 +292,8 @@ describe("MPollBody", () => {
// the ballot is still spoiled because the second answer is // the ballot is still spoiled because the second answer is
// invalid, even though we would ignore it if we continued. // invalid, even though we would ignore it if we continued.
const votes = [ const votes = [
responseEvent("@tr:example.com", "pizza", 12), responseEvent("@me:example.com", "pizza", 12),
responseEvent("@tr:example.com", ["pizza", "doesntexist"], 13), responseEvent("@me:example.com", ["pizza", "doesntexist"], 13),
responseEvent("@uy:example.com", "italian", 14), responseEvent("@uy:example.com", "italian", 14),
responseEvent("@uy:example.com", "doesntexist", 15), responseEvent("@uy:example.com", "doesntexist", 15),
]; ];
@ -278,8 +307,8 @@ describe("MPollBody", () => {
it("allows re-voting after a spoiled ballot", () => { it("allows re-voting after a spoiled ballot", () => {
const votes = [ const votes = [
responseEvent("@tr:example.com", "pizza", 12), responseEvent("@me:example.com", "pizza", 12),
responseEvent("@tr:example.com", ["pizza", "doesntexist"], 13), responseEvent("@me:example.com", ["pizza", "doesntexist"], 13),
responseEvent("@uy:example.com", "italian", 14), responseEvent("@uy:example.com", "italian", 14),
responseEvent("@uy:example.com", "doesntexist", 15), responseEvent("@uy:example.com", "doesntexist", 15),
responseEvent("@uy:example.com", "poutine", 16), responseEvent("@uy:example.com", "poutine", 16),
@ -389,7 +418,7 @@ describe("MPollBody", () => {
responseEvent("@op:example.com", "pizza", 12), responseEvent("@op:example.com", "pizza", 12),
responseEvent("@op:example.com", [], 13), responseEvent("@op:example.com", [], 13),
responseEvent("@op:example.com", "italian", 14), responseEvent("@op:example.com", "italian", 14),
responseEvent("@st:example.com", "wings", 15), responseEvent("@me:example.com", "wings", 15),
responseEvent("@qr:example.com", "italian", 16), responseEvent("@qr:example.com", "italian", 16),
]; ];
const body = newMPollBody(votes); const body = newMPollBody(votes);
@ -409,6 +438,18 @@ describe("MPollBody", () => {
clickRadio(body, "italian"); clickRadio(body, "italian");
expect(body).toMatchSnapshot(); expect(body).toMatchSnapshot();
}); });
it("renders a poll that I have not voted in", () => {
const votes = [
responseEvent("@op:example.com", "pizza", 12),
responseEvent("@op:example.com", [], 13),
responseEvent("@op:example.com", "italian", 14),
responseEvent("@yo:example.com", "wings", 15),
responseEvent("@qr:example.com", "italian", 16),
];
const body = newMPollBody(votes);
expect(body).toMatchSnapshot();
});
}); });
function newPollRelations(relationEvents: Array<MatrixEvent>): Relations { function newPollRelations(relationEvents: Array<MatrixEvent>): Relations {

View file

@ -1,5 +1,346 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MPollBody renders a poll that I have not voted in 1`] = `
<Wrapper
getRelationsForEvent={[Function]}
mxEvent={
Object {
"content": Object {
"org.matrix.msc1767.text": "What should we order for the party?
1. Pizza
2. Poutine
3. Italian
4. Wings",
"org.matrix.msc3381.poll.start": Object {
"answers": Array [
Object {
"id": "pizza",
"org.matrix.msc1767.text": "Pizza",
},
Object {
"id": "poutine",
"org.matrix.msc1767.text": "Poutine",
},
Object {
"id": "italian",
"org.matrix.msc1767.text": "Italian",
},
Object {
"id": "wings",
"org.matrix.msc1767.text": "Wings",
},
],
"kind": "org.matrix.msc3381.poll.disclosed",
"question": Object {
"org.matrix.msc1767.text": "What should we order for the party?",
},
},
},
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
}
}
>
<MPollBody
getRelationsForEvent={[Function]}
mxEvent={
Object {
"content": Object {
"org.matrix.msc1767.text": "What should we order for the party?
1. Pizza
2. Poutine
3. Italian
4. Wings",
"org.matrix.msc3381.poll.start": Object {
"answers": Array [
Object {
"id": "pizza",
"org.matrix.msc1767.text": "Pizza",
},
Object {
"id": "poutine",
"org.matrix.msc1767.text": "Poutine",
},
Object {
"id": "italian",
"org.matrix.msc1767.text": "Italian",
},
Object {
"id": "wings",
"org.matrix.msc1767.text": "Wings",
},
],
"kind": "org.matrix.msc3381.poll.disclosed",
"question": Object {
"org.matrix.msc1767.text": "What should we order for the party?",
},
},
},
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
}
}
>
<div
className="mx_MPollBody"
>
<h2>
What should we order for the party?
</h2>
<div
className="mx_MPollBody_allOptions"
>
<div
className="mx_MPollBody_option"
key="pizza"
onClick={[Function]}
>
<StyledRadioButton
checked={false}
childrenInLabel={true}
className=""
name="poll_answer_select-$mypoll"
onChange={[Function]}
value="pizza"
>
<label
className="mx_StyledRadioButton mx_StyledRadioButton_enabled"
>
<input
checked={false}
name="poll_answer_select-$mypoll"
onChange={[Function]}
type="radio"
value="pizza"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<div
className="mx_MPollBody_optionDescription"
>
<div
className="mx_MPollBody_optionText"
>
Pizza
</div>
<div
className="mx_MPollBody_optionVoteCount"
/>
</div>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<div
className="mx_MPollBody_popularityBackground"
>
<div
className="mx_MPollBody_popularityAmount"
style={
Object {
"width": "0%",
}
}
/>
</div>
</div>
<div
className="mx_MPollBody_option"
key="poutine"
onClick={[Function]}
>
<StyledRadioButton
checked={false}
childrenInLabel={true}
className=""
name="poll_answer_select-$mypoll"
onChange={[Function]}
value="poutine"
>
<label
className="mx_StyledRadioButton mx_StyledRadioButton_enabled"
>
<input
checked={false}
name="poll_answer_select-$mypoll"
onChange={[Function]}
type="radio"
value="poutine"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<div
className="mx_MPollBody_optionDescription"
>
<div
className="mx_MPollBody_optionText"
>
Poutine
</div>
<div
className="mx_MPollBody_optionVoteCount"
/>
</div>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<div
className="mx_MPollBody_popularityBackground"
>
<div
className="mx_MPollBody_popularityAmount"
style={
Object {
"width": "0%",
}
}
/>
</div>
</div>
<div
className="mx_MPollBody_option"
key="italian"
onClick={[Function]}
>
<StyledRadioButton
checked={false}
childrenInLabel={true}
className=""
name="poll_answer_select-$mypoll"
onChange={[Function]}
value="italian"
>
<label
className="mx_StyledRadioButton mx_StyledRadioButton_enabled"
>
<input
checked={false}
name="poll_answer_select-$mypoll"
onChange={[Function]}
type="radio"
value="italian"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<div
className="mx_MPollBody_optionDescription"
>
<div
className="mx_MPollBody_optionText"
>
Italian
</div>
<div
className="mx_MPollBody_optionVoteCount"
/>
</div>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<div
className="mx_MPollBody_popularityBackground"
>
<div
className="mx_MPollBody_popularityAmount"
style={
Object {
"width": "0%",
}
}
/>
</div>
</div>
<div
className="mx_MPollBody_option"
key="wings"
onClick={[Function]}
>
<StyledRadioButton
checked={false}
childrenInLabel={true}
className=""
name="poll_answer_select-$mypoll"
onChange={[Function]}
value="wings"
>
<label
className="mx_StyledRadioButton mx_StyledRadioButton_enabled"
>
<input
checked={false}
name="poll_answer_select-$mypoll"
onChange={[Function]}
type="radio"
value="wings"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<div
className="mx_MPollBody_optionDescription"
>
<div
className="mx_MPollBody_optionText"
>
Wings
</div>
<div
className="mx_MPollBody_optionVoteCount"
/>
</div>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<div
className="mx_MPollBody_popularityBackground"
>
<div
className="mx_MPollBody_popularityAmount"
style={
Object {
"width": "0%",
}
}
/>
</div>
</div>
</div>
<div
className="mx_MPollBody_totalVotes"
>
3 votes cast. Vote to see the results
</div>
</div>
</MPollBody>
</Wrapper>
`;
exports[`MPollBody renders a poll with local, non-local and invalid votes 1`] = ` exports[`MPollBody renders a poll with local, non-local and invalid votes 1`] = `
<Wrapper <Wrapper
getRelationsForEvent={[Function]} getRelationsForEvent={[Function]}
@ -478,9 +819,7 @@ exports[`MPollBody renders a poll with no votes 1`] = `
</div> </div>
<div <div
className="mx_MPollBody_optionVoteCount" className="mx_MPollBody_optionVoteCount"
> />
0 votes
</div>
</div> </div>
</div> </div>
<div <div
@ -540,9 +879,7 @@ exports[`MPollBody renders a poll with no votes 1`] = `
</div> </div>
<div <div
className="mx_MPollBody_optionVoteCount" className="mx_MPollBody_optionVoteCount"
> />
0 votes
</div>
</div> </div>
</div> </div>
<div <div
@ -602,9 +939,7 @@ exports[`MPollBody renders a poll with no votes 1`] = `
</div> </div>
<div <div
className="mx_MPollBody_optionVoteCount" className="mx_MPollBody_optionVoteCount"
> />
0 votes
</div>
</div> </div>
</div> </div>
<div <div
@ -664,9 +999,7 @@ exports[`MPollBody renders a poll with no votes 1`] = `
</div> </div>
<div <div
className="mx_MPollBody_optionVoteCount" className="mx_MPollBody_optionVoteCount"
> />
0 votes
</div>
</div> </div>
</div> </div>
<div <div
@ -691,7 +1024,7 @@ exports[`MPollBody renders a poll with no votes 1`] = `
<div <div
className="mx_MPollBody_totalVotes" className="mx_MPollBody_totalVotes"
> >
Based on 0 votes No votes cast
</div> </div>
</div> </div>
</MPollBody> </MPollBody>
@ -975,12 +1308,12 @@ exports[`MPollBody renders a poll with only non-local votes 1`] = `
</div> </div>
</div> </div>
<div <div
className="mx_MPollBody_option" className="mx_MPollBody_option mx_MPollBody_option_checked"
key="wings" key="wings"
onClick={[Function]} onClick={[Function]}
> >
<StyledRadioButton <StyledRadioButton
checked={false} checked={true}
childrenInLabel={true} childrenInLabel={true}
className="" className=""
name="poll_answer_select-$mypoll" name="poll_answer_select-$mypoll"
@ -988,10 +1321,10 @@ exports[`MPollBody renders a poll with only non-local votes 1`] = `
value="wings" value="wings"
> >
<label <label
className="mx_StyledRadioButton mx_StyledRadioButton_enabled" className="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_checked"
> >
<input <input
checked={false} checked={true}
name="poll_answer_select-$mypoll" name="poll_answer_select-$mypoll"
onChange={[Function]} onChange={[Function]}
type="radio" type="radio"