Support "closed" polls whose votes are not visible until they are ended (#7842)

This commit is contained in:
Andy Balaam 2022-02-21 10:21:35 +00:00 committed by GitHub
parent eca64d776a
commit f1e1b7be86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 248 additions and 11 deletions

View file

@ -37,6 +37,10 @@ limitations under the License.
}
}
p {
color: $secondary-content;
}
.mx_PollCreateDialog_option {
display: flex;
align-items: center;

View file

@ -16,7 +16,14 @@ limitations under the License.
import React, { ChangeEvent, createRef } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { IPartialEvent, M_POLL_KIND_DISCLOSED, M_POLL_START, PollStartEvent } from "matrix-events-sdk";
import {
IPartialEvent,
KNOWN_POLL_KIND,
M_POLL_KIND_DISCLOSED,
M_POLL_KIND_UNDISCLOSED,
M_POLL_START,
PollStartEvent,
} from "matrix-events-sdk";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import ScrollableBaseModal, { IScrollableBaseState } from "../dialogs/ScrollableBaseModal";
@ -43,6 +50,7 @@ interface IState extends IScrollableBaseState {
question: string;
options: string[];
busy: boolean;
kind: KNOWN_POLL_KIND;
autoFocusTarget: FocusTarget;
}
@ -60,6 +68,7 @@ function creatingInitialState(): IState {
question: "",
options: arraySeed("", DEFAULT_NUM_OPTIONS),
busy: false,
kind: M_POLL_KIND_DISCLOSED,
autoFocusTarget: FocusTarget.Topic,
};
}
@ -75,6 +84,7 @@ function editingInitialState(editingMxEvent: MatrixEvent): IState {
question: poll.question.text,
options: poll.answers.map(ans => ans.text),
busy: false,
kind: poll.kind,
autoFocusTarget: FocusTarget.Topic,
};
}
@ -131,7 +141,7 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
const pollStart = PollStartEvent.from(
this.state.question.trim(),
this.state.options.map(a => a.trim()).filter(a => !!a),
M_POLL_KIND_DISCLOSED,
this.state.kind,
).serialize();
if (!this.props.editingMxEvent) {
@ -190,6 +200,26 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
protected renderContent(): React.ReactNode {
return <div className="mx_PollCreateDialog">
<h2>{ _t("Poll type") }</h2>
<Field
element="select"
value={this.state.kind.name}
onChange={this.onPollTypeChange}
>
<option
key={M_POLL_KIND_DISCLOSED.name}
value={M_POLL_KIND_DISCLOSED.name}
>
{ _t("Open poll") }
</option>
<option
key={M_POLL_KIND_UNDISCLOSED.name}
value={M_POLL_KIND_UNDISCLOSED.name}
>
{ _t("Closed poll") }
</option>
</Field>
<p>{ pollTypeNotes(this.state.kind) }</p>
<h2>{ _t("What is your poll question or topic?") }</h2>
<Field
id='poll-topic-input'
@ -242,4 +272,22 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
}
</div>;
}
onPollTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
this.setState({
kind: (
M_POLL_KIND_DISCLOSED.matches(e.target.value)
? M_POLL_KIND_DISCLOSED
: M_POLL_KIND_UNDISCLOSED
),
});
};
}
function pollTypeNotes(kind: KNOWN_POLL_KIND): string {
if (M_POLL_KIND_DISCLOSED.matches(kind.name)) {
return _t("Voters see results as soon as they have voted");
} else {
return _t("Results are only revealed when you end the poll");
}
}

View file

@ -21,6 +21,7 @@ import { Relations } from 'matrix-js-sdk/src/models/relations';
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
import {
M_POLL_END,
M_POLL_KIND_DISCLOSED,
M_POLL_RESPONSE,
M_POLL_START,
NamespacedValue,
@ -431,6 +432,11 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const winCount = Math.max(...votes.values());
const userId = this.context.getUserId();
const myVote = userVotes.get(userId)?.answers[0];
const disclosed = M_POLL_KIND_DISCLOSED.matches(poll.kind.name);
// Disclosed: votes are hidden until I vote or the poll ends
// Undisclosed: votes are hidden until poll ends
const showResults = ended || (disclosed && myVote !== undefined);
let totalText: string;
if (ended) {
@ -438,6 +444,8 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
"Final result based on %(count)s votes",
{ count: totalVotes },
);
} else if (!disclosed) {
totalText = _t("Results will be visible when the poll is ended");
} else if (myVote === undefined) {
if (totalVotes === 0) {
totalText = _t("No votes cast");
@ -465,8 +473,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
let answerVotes = 0;
let votesText = "";
// Votes are hidden until I vote or the poll ends
if (ended || myVote !== undefined) {
if (showResults) {
answerVotes = votes.get(answer.id) ?? 0;
votesText = _t("%(count)s votes", { count: answerVotes });
}

View file

@ -2134,6 +2134,7 @@
"Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.",
"Final result based on %(count)s votes|other": "Final result based on %(count)s votes",
"Final result based on %(count)s votes|one": "Final result based on %(count)s vote",
"Results will be visible when the poll is ended": "Results will be visible when the poll is ended",
"No votes cast": "No votes cast",
"%(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",
@ -2321,6 +2322,9 @@
"Done": "Done",
"Failed to post poll": "Failed to post poll",
"Sorry, the poll you tried to create was not posted.": "Sorry, the poll you tried to create was not posted.",
"Poll type": "Poll type",
"Open poll": "Open poll",
"Closed poll": "Closed poll",
"What is your poll question or topic?": "What is your poll question or topic?",
"Question or topic": "Question or topic",
"Write something...": "Write something...",
@ -2328,6 +2332,8 @@
"Option %(number)s": "Option %(number)s",
"Write an option": "Write an option",
"Add option": "Add option",
"Voters see results as soon as they have voted": "Voters see results as soon as they have voted",
"Results are only revealed when you end the poll": "Results are only revealed when you end the poll",
"Power level": "Power level",
"Custom level": "Custom level",
"QR Code": "QR Code",

View file

@ -19,7 +19,13 @@ import '../../../skinned-sdk';
import React from "react";
import { mount, ReactWrapper } from "enzyme";
import { Room } from "matrix-js-sdk/src/models/room";
import { M_POLL_KIND_DISCLOSED, M_POLL_START, M_TEXT, PollStartEvent } from 'matrix-events-sdk';
import {
M_POLL_KIND_DISCLOSED,
M_POLL_KIND_UNDISCLOSED,
M_POLL_START,
M_TEXT,
PollStartEvent,
} from 'matrix-events-sdk';
import { IContent, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import * as TestUtils from "../../../test-utils";
@ -133,6 +139,71 @@ describe("PollCreateDialog", () => {
expect(submitIsDisabled(dialog)).toBe(false);
});
it("shows the open poll description at first", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
});
it("shows the open poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
changeKind(dialog, M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description when editing a closed poll", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_UNDISCLOSED,
).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
});
it("displays a spinner after submitting", () => {
TestUtils.stubClient();
MatrixClientPeg.get().sendEvent = jest.fn(() => Promise.resolve());
@ -236,6 +307,7 @@ describe("PollCreateDialog", () => {
changeValue(dialog, "Question or topic", "Poll Q updated");
changeValue(dialog, "Option 2", "Answer 2 updated");
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
dialog.find("button").simulate("click");
expect(sentEventContent).toEqual(
@ -253,7 +325,7 @@ describe("PollCreateDialog", () => {
[M_TEXT.name]: "Answer 2 updated",
},
],
"kind": M_POLL_KIND_DISCLOSED.name,
"kind": M_POLL_KIND_UNDISCLOSED.name,
"max_selections": 1,
"question": {
"body": "Poll Q updated",
@ -289,6 +361,13 @@ function changeValue(wrapper: ReactWrapper, labelText: string, value: string) {
);
}
function changeKind(wrapper: ReactWrapper, value: string) {
wrapper.find("select").simulate(
"change",
{ target: { value: value } },
);
}
function submitIsDisabled(wrapper: ReactWrapper) {
return wrapper.find('button[type="submit"]').prop("aria-disabled") === true;
}

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PollCreateDialog renders a blank poll 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" aria-disabled=\\"true\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders a blank poll 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_1\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_1\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" aria-disabled=\\"true\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders a question and some options 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"How many turnips is the optimal number?\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"As many as my neighbour\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"The question is meaningless\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_2\\" maxlength=\\"340\\" label=\\"Option 3\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Mu\\"><label for=\\"pollcreate_option_2\\">Option 3</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders a question and some options 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_4\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_4\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"How many turnips is the optimal number?\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"As many as my neighbour\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"The question is meaningless\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_2\\" maxlength=\\"340\\" label=\\"Option 3\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Mu\\"><label for=\\"pollcreate_option_2\\">Option 3</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders info from a previous event 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Edit poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"Poll Q\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Answer 1\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Answer 2\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary\\">Done</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders info from a previous event 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Edit poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_5\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_5\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"Poll Q\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Answer 1\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Answer 2\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary\\">Done</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;

View file

@ -23,6 +23,7 @@ import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
import {
M_POLL_END,
M_POLL_KIND_DISCLOSED,
M_POLL_KIND_UNDISCLOSED,
M_POLL_RESPONSE,
M_POLL_START,
M_POLL_START_EVENT_CONTENT,
@ -474,6 +475,60 @@ describe("MPollBody", () => {
).toBe(20);
});
it("hides scores if I voted but the poll is undisclosed", () => {
const votes = [
responseEvent("@me:example.com", "pizza"),
responseEvent("@alice:example.com", "pizza"),
responseEvent("@bellc:example.com", "pizza"),
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const body = newMPollBody(votes, [], null, false);
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(
"Results will be visible when the poll is ended");
});
it("highlights my vote if the poll is undisclosed", () => {
const votes = [
responseEvent("@me:example.com", "pizza"),
responseEvent("@alice:example.com", "poutine"),
responseEvent("@bellc:example.com", "poutine"),
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const body = newMPollBody(votes, [], null, false);
// My vote is marked
expect(body.find('input[value="pizza"]').prop("checked")).toBeTruthy();
// Sanity: other items are not checked
expect(body.find('input[value="poutine"]').prop("checked")).toBeFalsy();
});
it("shows scores if the poll is undisclosed but ended", () => {
const votes = [
responseEvent("@me:example.com", "pizza"),
responseEvent("@alice:example.com", "pizza"),
responseEvent("@bellc:example.com", "pizza"),
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const ends = [
endEvent("@me:example.com", 12),
];
const body = newMPollBody(votes, ends, null, false);
expect(endedVotesCount(body, "pizza")).toBe("3 votes");
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("1 vote");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"Final result based on 5 votes");
});
it("sends a vote event when I choose an option", () => {
const receivedEvents = [];
MatrixClientPeg.matrixClient.sendEvent = (
@ -993,6 +1048,34 @@ describe("MPollBody", () => {
const body = newMPollBody(votes, ends);
expect(body).toMatchSnapshot();
});
it("renders an undisclosed, unfinished poll", () => {
const votes = [
responseEvent("@ed:example.com", "pizza", 12),
responseEvent("@rf:example.com", "pizza", 12),
responseEvent("@th:example.com", "wings", 13),
responseEvent("@yh:example.com", "wings", 14),
responseEvent("@th:example.com", "poutine", 13),
responseEvent("@yh:example.com", "poutine", 14),
];
const ends = [];
const body = newMPollBody(votes, ends, null, false);
expect(body.html()).toMatchSnapshot();
});
it("renders an undisclosed, finished poll", () => {
const votes = [
responseEvent("@ed:example.com", "pizza", 12),
responseEvent("@rf:example.com", "pizza", 12),
responseEvent("@th:example.com", "wings", 13),
responseEvent("@yh:example.com", "wings", 14),
responseEvent("@th:example.com", "poutine", 13),
responseEvent("@yh:example.com", "poutine", 14),
];
const ends = [endEvent("@me:example.com", 25)];
const body = newMPollBody(votes, ends, null, false);
expect(body.html()).toMatchSnapshot();
});
});
function newVoteRelations(relationEvents: Array<MatrixEvent>): Relations {
@ -1018,12 +1101,13 @@ function newMPollBody(
relationEvents: Array<MatrixEvent>,
endEvents: Array<MatrixEvent> = [],
answers?: POLL_ANSWER[],
disclosed = true,
): ReactWrapper {
const mxEvent = new MatrixEvent({
"type": M_POLL_START.name,
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"content": newPollStart(answers),
"content": newPollStart(answers, null, disclosed),
});
return newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
}
@ -1096,6 +1180,7 @@ function endedVotesCount(wrapper: ReactWrapper, value: string): string {
function newPollStart(
answers?: POLL_ANSWER[],
question?: string,
disclosed = true,
): M_POLL_START_EVENT_CONTENT {
if (!answers) {
answers = [
@ -1121,7 +1206,11 @@ function newPollStart(
"question": {
[M_TEXT.name]: question,
},
"kind": M_POLL_KIND_DISCLOSED.name,
"kind": (
disclosed
? M_POLL_KIND_DISCLOSED.name
: M_POLL_KIND_UNDISCLOSED.name
),
"answers": answers,
},
[M_TEXT.name]: fallback,

View file

@ -2872,3 +2872,7 @@ exports[`MPollBody renders a poll with only non-local votes 1`] = `
</MPollBody>
</Wrapper>
`;
exports[`MPollBody renders an undisclosed, finished poll 1`] = `"<div class=\\"mx_MPollBody\\"><h2>What should we order for the party?</h2><div class=\\"mx_MPollBody_allOptions\\"><div class=\\"mx_MPollBody_option mx_MPollBody_option_checked mx_MPollBody_option_ended\\"><div class=\\"mx_MPollBody_endedOption mx_MPollBody_endedOptionWinner\\" data-value=\\"pizza\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Pizza</div><div class=\\"mx_MPollBody_optionVoteCount\\">2 votes</div></div></div><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 50%;\\"></div></div></div><div class=\\"mx_MPollBody_option mx_MPollBody_option_ended\\"><div class=\\"mx_MPollBody_endedOption\\" data-value=\\"poutine\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Poutine</div><div class=\\"mx_MPollBody_optionVoteCount\\">0 votes</div></div></div><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div><div class=\\"mx_MPollBody_option mx_MPollBody_option_ended\\"><div class=\\"mx_MPollBody_endedOption\\" data-value=\\"italian\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Italian</div><div class=\\"mx_MPollBody_optionVoteCount\\">0 votes</div></div></div><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div><div class=\\"mx_MPollBody_option mx_MPollBody_option_checked mx_MPollBody_option_ended\\"><div class=\\"mx_MPollBody_endedOption mx_MPollBody_endedOptionWinner\\" data-value=\\"wings\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Wings</div><div class=\\"mx_MPollBody_optionVoteCount\\">2 votes</div></div></div><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 50%;\\"></div></div></div></div><div class=\\"mx_MPollBody_totalVotes\\">Final result based on 4 votes</div></div>"`;
exports[`MPollBody renders an undisclosed, unfinished poll 1`] = `"<div class=\\"mx_MPollBody\\"><h2>What should we order for the party?</h2><div class=\\"mx_MPollBody_allOptions\\"><div class=\\"mx_MPollBody_option\\"><label class=\\"mx_StyledRadioButton mx_MPollBody_live-option mx_StyledRadioButton_enabled\\"><input type=\\"radio\\" name=\\"poll_answer_select-$mypoll\\" value=\\"pizza\\"><div><div></div></div><div class=\\"mx_StyledRadioButton_content\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Pizza</div><div class=\\"mx_MPollBody_optionVoteCount\\"></div></div></div><div class=\\"mx_StyledRadioButton_spacer\\"></div></label><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div><div class=\\"mx_MPollBody_option\\"><label class=\\"mx_StyledRadioButton mx_MPollBody_live-option mx_StyledRadioButton_enabled\\"><input type=\\"radio\\" name=\\"poll_answer_select-$mypoll\\" value=\\"poutine\\"><div><div></div></div><div class=\\"mx_StyledRadioButton_content\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Poutine</div><div class=\\"mx_MPollBody_optionVoteCount\\"></div></div></div><div class=\\"mx_StyledRadioButton_spacer\\"></div></label><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div><div class=\\"mx_MPollBody_option\\"><label class=\\"mx_StyledRadioButton mx_MPollBody_live-option mx_StyledRadioButton_enabled\\"><input type=\\"radio\\" name=\\"poll_answer_select-$mypoll\\" value=\\"italian\\"><div><div></div></div><div class=\\"mx_StyledRadioButton_content\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Italian</div><div class=\\"mx_MPollBody_optionVoteCount\\"></div></div></div><div class=\\"mx_StyledRadioButton_spacer\\"></div></label><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div><div class=\\"mx_MPollBody_option\\"><label class=\\"mx_StyledRadioButton mx_MPollBody_live-option mx_StyledRadioButton_enabled\\"><input type=\\"radio\\" name=\\"poll_answer_select-$mypoll\\" value=\\"wings\\"><div><div></div></div><div class=\\"mx_StyledRadioButton_content\\"><div class=\\"mx_MPollBody_optionDescription\\"><div class=\\"mx_MPollBody_optionText\\">Wings</div><div class=\\"mx_MPollBody_optionVoteCount\\"></div></div></div><div class=\\"mx_StyledRadioButton_spacer\\"></div></label><div class=\\"mx_MPollBody_popularityBackground\\"><div class=\\"mx_MPollBody_popularityAmount\\" style=\\"width: 0%;\\"></div></div></div></div><div class=\\"mx_MPollBody_totalVotes\\">Results will be visible when the poll is ended</div></div>"`;