Allow ending polls (#7305)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
697b5d28b3
commit
2b52e17a80
12 changed files with 2814 additions and 680 deletions
|
@ -20,11 +20,23 @@ import { mount, ReactWrapper } from "enzyme";
|
|||
import sdk from "../../../skinned-sdk";
|
||||
import * as TestUtils from "../../../test-utils";
|
||||
|
||||
import { Callback, IContent, MatrixEvent } from "matrix-js-sdk";
|
||||
import { Callback, IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk";
|
||||
import { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { IPollAnswer, IPollContent, POLL_RESPONSE_EVENT_TYPE } from "../../../../src/polls/consts";
|
||||
import { UserVote, allVotes } from "../../../../src/components/views/messages/MPollBody";
|
||||
import {
|
||||
IPollAnswer,
|
||||
IPollContent,
|
||||
POLL_END_EVENT_TYPE,
|
||||
POLL_RESPONSE_EVENT_TYPE,
|
||||
TEXT_NODE_TYPE,
|
||||
} from "../../../../src/polls/consts";
|
||||
import {
|
||||
UserVote,
|
||||
allVotes,
|
||||
findTopAnswer,
|
||||
pollEndTs,
|
||||
isPollEnded,
|
||||
} from "../../../../src/components/views/messages/MPollBody";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
||||
|
||||
|
@ -37,10 +49,18 @@ MatrixClientPeg.matrixClient = {
|
|||
getUserId: () => "@me:example.com",
|
||||
sendEvent: () => Promise.resolve({ "event_id": "fake_send_id" }),
|
||||
};
|
||||
setRedactionAllowedForMeOnly(MatrixClientPeg.matrixClient);
|
||||
|
||||
describe("MPollBody", () => {
|
||||
it("finds no votes if there are none", () => {
|
||||
expect(allVotes(newPollRelations([]))).toEqual([]);
|
||||
expect(
|
||||
allVotes(
|
||||
{ getRoomId: () => "$room" } as MatrixEvent,
|
||||
MatrixClientPeg.get(),
|
||||
newVoteRelations([]),
|
||||
newEndRelations([]),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("can find all the valid responses to a poll", () => {
|
||||
|
@ -48,8 +68,15 @@ describe("MPollBody", () => {
|
|||
const ev2 = responseEvent();
|
||||
const badEvent = badResponseEvent();
|
||||
|
||||
const pollRelations = newPollRelations([ev1, badEvent, ev2]);
|
||||
expect(allVotes(pollRelations)).toEqual([
|
||||
const voteRelations = newVoteRelations([ev1, badEvent, ev2]);
|
||||
expect(
|
||||
allVotes(
|
||||
{ getRoomId: () => "$room" } as MatrixEvent,
|
||||
MatrixClientPeg.get(),
|
||||
voteRelations,
|
||||
newEndRelations([]),
|
||||
),
|
||||
).toEqual([
|
||||
new UserVote(
|
||||
ev1.getTs(),
|
||||
ev1.getSender(),
|
||||
|
@ -63,6 +90,71 @@ describe("MPollBody", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("finds the first end poll event", () => {
|
||||
const endRelations = newEndRelations([
|
||||
endEvent("@me:example.com", 25),
|
||||
endEvent("@me:example.com", 12),
|
||||
endEvent("@me:example.com", 45),
|
||||
endEvent("@me:example.com", 13),
|
||||
]);
|
||||
|
||||
const matrixClient = TestUtils.createTestClient();
|
||||
setRedactionAllowedForMeOnly(matrixClient);
|
||||
|
||||
expect(
|
||||
pollEndTs(
|
||||
{ getRoomId: () => "$room" } as MatrixEvent,
|
||||
matrixClient,
|
||||
endRelations,
|
||||
),
|
||||
).toBe(12);
|
||||
});
|
||||
|
||||
it("ignores unauthorised end poll event when finding end ts", () => {
|
||||
const endRelations = newEndRelations([
|
||||
endEvent("@me:example.com", 25),
|
||||
endEvent("@unauthorised:example.com", 12),
|
||||
endEvent("@me:example.com", 45),
|
||||
endEvent("@me:example.com", 13),
|
||||
]);
|
||||
|
||||
const matrixClient = TestUtils.createTestClient();
|
||||
setRedactionAllowedForMeOnly(matrixClient);
|
||||
|
||||
expect(
|
||||
pollEndTs(
|
||||
{ getRoomId: () => "$room" } as MatrixEvent,
|
||||
matrixClient,
|
||||
endRelations,
|
||||
),
|
||||
).toBe(13);
|
||||
});
|
||||
|
||||
it("counts only votes before the end poll event", () => {
|
||||
const voteRelations = newVoteRelations([
|
||||
responseEvent("sf@matrix.org", "wings", 13),
|
||||
responseEvent("jr@matrix.org", "poutine", 40),
|
||||
responseEvent("ak@matrix.org", "poutine", 37),
|
||||
responseEvent("id@matrix.org", "wings", 13),
|
||||
responseEvent("ps@matrix.org", "wings", 19),
|
||||
]);
|
||||
const endRelations = newEndRelations([
|
||||
endEvent("@me:example.com", 25),
|
||||
]);
|
||||
expect(
|
||||
allVotes(
|
||||
{ getRoomId: () => "$room" } as MatrixEvent,
|
||||
MatrixClientPeg.get(),
|
||||
voteRelations,
|
||||
endRelations,
|
||||
),
|
||||
).toEqual([
|
||||
new UserVote(13, "sf@matrix.org", ["wings"]),
|
||||
new UserVote(13, "id@matrix.org", ["wings"]),
|
||||
new UserVote(19, "ps@matrix.org", ["wings"]),
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders no votes if none were made", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
|
@ -88,6 +180,27 @@ describe("MPollBody", () => {
|
|||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes");
|
||||
});
|
||||
|
||||
it("ignores end poll events from unauthorised users", () => {
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "pizza"),
|
||||
responseEvent("@bellc:example.com", "pizza"),
|
||||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@notallowed:example.com", 12),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
// Even though an end event was sent, we render the poll as unfinished
|
||||
// because this person is not allowed to send these events
|
||||
expect(votesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
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"),
|
||||
|
@ -185,12 +298,12 @@ describe("MPollBody", () => {
|
|||
const votes = [responseEvent("@me:example.com", "pizza", 100)];
|
||||
const body = newMPollBody(votes);
|
||||
const props: IBodyProps = body.instance().props as IBodyProps;
|
||||
const pollRelations: Relations = props.getRelationsForEvent(
|
||||
const voteRelations: Relations = props.getRelationsForEvent(
|
||||
"$mypoll", "m.reference", POLL_RESPONSE_EVENT_TYPE.name);
|
||||
clickRadio(body, "pizza");
|
||||
|
||||
// When a new vote from me comes in
|
||||
pollRelations.addEvent(responseEvent("@me:example.com", "wings", 101));
|
||||
voteRelations.addEvent(responseEvent("@me:example.com", "wings", 101));
|
||||
|
||||
// Then the new vote is counted, not the old one
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
|
@ -206,12 +319,12 @@ describe("MPollBody", () => {
|
|||
const votes = [responseEvent("@me:example.com", "pizza")];
|
||||
const body = newMPollBody(votes);
|
||||
const props: IBodyProps = body.instance().props as IBodyProps;
|
||||
const pollRelations: Relations = props.getRelationsForEvent(
|
||||
const voteRelations: Relations = props.getRelationsForEvent(
|
||||
"$mypoll", "m.reference", POLL_RESPONSE_EVENT_TYPE.name);
|
||||
clickRadio(body, "pizza");
|
||||
|
||||
// When a new vote from someone else comes in
|
||||
pollRelations.addEvent(responseEvent("@xx:example.com", "wings", 101));
|
||||
voteRelations.addEvent(responseEvent("@xx:example.com", "wings", 101));
|
||||
|
||||
// Then my vote is still for pizza
|
||||
// NOTE: the new event does not affect the counts for other people -
|
||||
|
@ -314,6 +427,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@uy:example.com", "poutine", 16),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(4);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
|
@ -324,7 +438,8 @@ describe("MPollBody", () => {
|
|||
it("renders nothing if poll has no answers", () => {
|
||||
const answers = [];
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes, answers);
|
||||
const ends = [];
|
||||
const body = newMPollBody(votes, ends, answers);
|
||||
expect(body.html()).toBe("");
|
||||
});
|
||||
|
||||
|
@ -333,7 +448,8 @@ describe("MPollBody", () => {
|
|||
return { "id": `id${i}`, "org.matrix.msc1767.text": `Name ${i}` };
|
||||
});
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes, answers);
|
||||
const ends = [];
|
||||
const body = newMPollBody(votes, ends, answers);
|
||||
expect(body.html()).toBe("");
|
||||
});
|
||||
|
||||
|
@ -407,6 +523,309 @@ describe("MPollBody", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("sends no events when I click in an ended poll", () => {
|
||||
const receivedEvents = [];
|
||||
MatrixClientPeg.matrixClient.sendEvent = (
|
||||
roomId: string,
|
||||
eventType: string,
|
||||
content: IContent,
|
||||
txnId?: string,
|
||||
callback?: Callback,
|
||||
): Promise<ISendEventResponse> => {
|
||||
receivedEvents.push( { roomId, eventType, content, txnId, callback } );
|
||||
return Promise.resolve({ "event_id": "fake_tracked_send_id" });
|
||||
};
|
||||
|
||||
const ends = [
|
||||
endEvent("@me:example.com", 25),
|
||||
];
|
||||
const votes = [
|
||||
responseEvent("@uy:example.com", "wings", 15),
|
||||
responseEvent("@uy:example.com", "poutine", 15),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
clickEndedOption(body, "wings");
|
||||
clickEndedOption(body, "italian");
|
||||
clickEndedOption(body, "poutine");
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
it("finds the top answer among several votes", () => {
|
||||
// 2 votes for poutine, 1 for pizza. "me" made an invalid vote.
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "pizza", 12),
|
||||
responseEvent("@me:example.com", ["pizza", "doesntexist"], 13),
|
||||
responseEvent("@uy:example.com", "italian", 14),
|
||||
responseEvent("@uy:example.com", "doesntexist", 15),
|
||||
responseEvent("@uy:example.com", "poutine", 16),
|
||||
responseEvent("@ab:example.com", "pizza", 17),
|
||||
responseEvent("@fa:example.com", "poutine", 18),
|
||||
];
|
||||
|
||||
expect(runFindTopAnswer(votes, [])).toEqual("Poutine");
|
||||
});
|
||||
|
||||
it("finds all top answers when there is a draw", () => {
|
||||
const votes = [
|
||||
responseEvent("@uy:example.com", "italian", 14),
|
||||
responseEvent("@ab:example.com", "pizza", 17),
|
||||
responseEvent("@fa:example.com", "poutine", 18),
|
||||
];
|
||||
expect(runFindTopAnswer(votes, [])).toEqual("Italian, Pizza and Poutine");
|
||||
});
|
||||
|
||||
it("finds all top answers ignoring late votes", () => {
|
||||
const votes = [
|
||||
responseEvent("@uy:example.com", "italian", 14),
|
||||
responseEvent("@ab:example.com", "pizza", 17),
|
||||
responseEvent("@io:example.com", "poutine", 30), // Late
|
||||
responseEvent("@fa:example.com", "poutine", 18),
|
||||
responseEvent("@of:example.com", "poutine", 31), // Late
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@me:example.com", 25),
|
||||
];
|
||||
expect(runFindTopAnswer(votes, ends)).toEqual("Italian, Pizza and Poutine");
|
||||
});
|
||||
|
||||
it("is silent about the top answer if there are no votes", () => {
|
||||
expect(runFindTopAnswer([], [])).toEqual("");
|
||||
});
|
||||
|
||||
it("is silent about the top answer if there are no votes when ended", () => {
|
||||
expect(runFindTopAnswer([], [endEvent("@me:example.com", 13)])).toEqual("");
|
||||
});
|
||||
|
||||
it("shows non-radio buttons if the poll is ended", () => {
|
||||
const events = [endEvent()];
|
||||
const body = newMPollBody([], events);
|
||||
expect(body.find(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("counts votes as normal if the poll is ended", () => {
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "pizza", 12),
|
||||
responseEvent("@me:example.com", "wings", 20), // latest me
|
||||
responseEvent("@qbert:example.com", "pizza", 14),
|
||||
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
|
||||
responseEvent("@qbert:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("0 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 2 votes");
|
||||
});
|
||||
|
||||
it("counts a single vote as normal if the poll is ended", () => {
|
||||
const votes = [responseEvent("@qbert:example.com", "poutine", 16)];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("0 votes");
|
||||
expect(
|
||||
body.find(".mx_MPollBody_totalVotes").text(),
|
||||
).toBe("Final result based on 1 vote");
|
||||
});
|
||||
|
||||
it("shows ended vote counts of different numbers", () => {
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "wings", 20),
|
||||
responseEvent("@qb:example.com", "wings", 14),
|
||||
responseEvent("@xy:example.com", "wings", 15),
|
||||
responseEvent("@fg:example.com", "pizza", 15),
|
||||
responseEvent("@hi:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
expect(body.find(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(0);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(
|
||||
body.find(".mx_MPollBody_totalVotes").text(),
|
||||
).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after poll ended", () => {
|
||||
const votes = [
|
||||
responseEvent("@sd:example.com", "wings", 30), // Late
|
||||
responseEvent("@ff:example.com", "wings", 20),
|
||||
responseEvent("@ut:example.com", "wings", 14),
|
||||
responseEvent("@iu:example.com", "wings", 15),
|
||||
responseEvent("@jf:example.com", "wings", 35), // Late
|
||||
responseEvent("@wf:example.com", "pizza", 15),
|
||||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(
|
||||
body.find(".mx_MPollBody_totalVotes").text(),
|
||||
).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("counts votes that arrived after an unauthorised poll end event", () => {
|
||||
const votes = [
|
||||
responseEvent("@sd:example.com", "wings", 30), // Late
|
||||
responseEvent("@ff:example.com", "wings", 20),
|
||||
responseEvent("@ut:example.com", "wings", 14),
|
||||
responseEvent("@iu:example.com", "wings", 15),
|
||||
responseEvent("@jf:example.com", "wings", 35), // Late
|
||||
responseEvent("@wf:example.com", "pizza", 15),
|
||||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@unauthorised:example.com", 5), // Should be ignored
|
||||
endEvent("@me:example.com", 25),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(
|
||||
body.find(".mx_MPollBody_totalVotes").text(),
|
||||
).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after the first end poll event", () => {
|
||||
// From MSC3381:
|
||||
// "Votes sent on or before the end event's timestamp are valid votes"
|
||||
|
||||
const votes = [
|
||||
responseEvent("@sd:example.com", "wings", 30), // Late
|
||||
responseEvent("@ff:example.com", "wings", 20),
|
||||
responseEvent("@ut:example.com", "wings", 14),
|
||||
responseEvent("@iu:example.com", "wings", 25), // Just on time
|
||||
responseEvent("@jf:example.com", "wings", 35), // Late
|
||||
responseEvent("@wf:example.com", "pizza", 15),
|
||||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@me:example.com", 65),
|
||||
endEvent("@me:example.com", 25),
|
||||
endEvent("@me:example.com", 75),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(
|
||||
body.find(".mx_MPollBody_totalVotes").text(),
|
||||
).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("highlights the winning vote in an ended poll", () => {
|
||||
// Given I voted for pizza but the winner is wings
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "pizza", 20),
|
||||
responseEvent("@qb:example.com", "wings", 14),
|
||||
responseEvent("@xy:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
// Then the winner is highlighted
|
||||
expect(endedVoteChecked(body, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(body, "pizza")).toBe(false);
|
||||
|
||||
// Double-check by looking for the endedOptionWinner class
|
||||
expect(
|
||||
endedVoteDiv(body, "wings").hasClass("mx_MPollBody_endedOptionWinner"),
|
||||
).toBe(true);
|
||||
expect(
|
||||
endedVoteDiv(body, "pizza").hasClass("mx_MPollBody_endedOptionWinner"),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("highlights multiple winning votes", () => {
|
||||
const votes = [
|
||||
responseEvent("@me:example.com", "pizza", 20),
|
||||
responseEvent("@xy:example.com", "wings", 15),
|
||||
responseEvent("@fg:example.com", "poutine", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVoteChecked(body, "pizza")).toBe(true);
|
||||
expect(endedVoteChecked(body, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(body, "poutine")).toBe(true);
|
||||
expect(endedVoteChecked(body, "italian")).toBe(false);
|
||||
expect(body.find(".mx_MPollBody_option_checked")).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("highlights nothing if poll has no votes", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody([], ends);
|
||||
expect(body.find(".mx_MPollBody_option_checked")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("says poll is not ended if there is no end event", () => {
|
||||
const ends = [];
|
||||
expect(runIsPollEnded(ends)).toBe(false);
|
||||
});
|
||||
|
||||
it("says poll is ended if there is an end event", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
expect(runIsPollEnded(ends)).toBe(true);
|
||||
});
|
||||
|
||||
it("says poll is not ended if endRelations is undefined", () => {
|
||||
const pollEvent = new MatrixEvent();
|
||||
const matrixClient = TestUtils.createTestClient();
|
||||
setRedactionAllowedForMeOnly(matrixClient);
|
||||
expect(isPollEnded(pollEvent, matrixClient, undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("says poll is not ended if asking for relations returns undefined", () => {
|
||||
const pollEvent = new MatrixEvent({
|
||||
"event_id": "$mypoll",
|
||||
"room_id": "#myroom:example.com",
|
||||
"content": newPollStart([]),
|
||||
});
|
||||
MatrixClientPeg.matrixClient.getRoom = () => {
|
||||
return {
|
||||
currentState: {
|
||||
maySendRedactionForEvent: (_evt: MatrixEvent, userId: string) => {
|
||||
return userId === "@me:example.com";
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
const getRelationsForEvent =
|
||||
(eventId: string, relationType: string, eventType: string) => {
|
||||
expect(eventId).toBe("$mypoll");
|
||||
expect(relationType).toBe("m.reference");
|
||||
expect(eventType).toBe(POLL_END_EVENT_TYPE.name);
|
||||
return undefined;
|
||||
};
|
||||
expect(
|
||||
isPollEnded(
|
||||
pollEvent,
|
||||
MatrixClientPeg.get(),
|
||||
getRelationsForEvent,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("renders a poll with no votes", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
|
@ -450,25 +869,75 @@ describe("MPollBody", () => {
|
|||
const body = newMPollBody(votes);
|
||||
expect(body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll with no votes", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody([], ends);
|
||||
expect(body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll", () => {
|
||||
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 ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll with multiple winners", () => {
|
||||
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);
|
||||
expect(body).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
function newPollRelations(relationEvents: Array<MatrixEvent>): Relations {
|
||||
const pollRelations = new Relations(
|
||||
"m.reference", POLL_RESPONSE_EVENT_TYPE.name, null);
|
||||
function newVoteRelations(relationEvents: Array<MatrixEvent>): Relations {
|
||||
return newRelations(relationEvents, POLL_RESPONSE_EVENT_TYPE.name);
|
||||
}
|
||||
|
||||
function newEndRelations(relationEvents: Array<MatrixEvent>): Relations {
|
||||
return newRelations(relationEvents, POLL_END_EVENT_TYPE.name);
|
||||
}
|
||||
|
||||
function newRelations(
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
eventType: string,
|
||||
): Relations {
|
||||
const voteRelations = new Relations("m.reference", eventType, null);
|
||||
for (const ev of relationEvents) {
|
||||
pollRelations.addEvent(ev);
|
||||
voteRelations.addEvent(ev);
|
||||
}
|
||||
return pollRelations;
|
||||
return voteRelations;
|
||||
}
|
||||
|
||||
function newMPollBody(
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
answers?: IPollAnswer[],
|
||||
): ReactWrapper {
|
||||
const pollRelations = new Relations(
|
||||
const voteRelations = new Relations(
|
||||
"m.reference", POLL_RESPONSE_EVENT_TYPE.name, null);
|
||||
for (const ev of relationEvents) {
|
||||
pollRelations.addEvent(ev);
|
||||
voteRelations.addEvent(ev);
|
||||
}
|
||||
|
||||
const endRelations = new Relations(
|
||||
"m.reference", POLL_END_EVENT_TYPE.name, null);
|
||||
for (const ev of endEvents) {
|
||||
endRelations.addEvent(ev);
|
||||
}
|
||||
|
||||
return mount(<MPollBody
|
||||
|
@ -481,15 +950,28 @@ function newMPollBody(
|
|||
(eventId: string, relationType: string, eventType: string) => {
|
||||
expect(eventId).toBe("$mypoll");
|
||||
expect(relationType).toBe("m.reference");
|
||||
expect(eventType).toBe(POLL_RESPONSE_EVENT_TYPE.name);
|
||||
return pollRelations;
|
||||
if (POLL_RESPONSE_EVENT_TYPE.matches(eventType)) {
|
||||
return voteRelations;
|
||||
} else if (POLL_END_EVENT_TYPE.matches(eventType)) {
|
||||
return endRelations;
|
||||
} else {
|
||||
fail("Unexpected eventType: " + eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
/>);
|
||||
}
|
||||
|
||||
function clickRadio(wrapper: ReactWrapper, value: string) {
|
||||
wrapper.find(`StyledRadioButton[value="${value}"]`).simulate("click");
|
||||
const div = wrapper.find(`StyledRadioButton[value="${value}"]`);
|
||||
expect(div).toHaveLength(1);
|
||||
div.simulate("click");
|
||||
}
|
||||
|
||||
function clickEndedOption(wrapper: ReactWrapper, value: string) {
|
||||
const div = wrapper.find(`div[data-value="${value}"]`);
|
||||
expect(div).toHaveLength(1);
|
||||
div.simulate("click");
|
||||
}
|
||||
|
||||
function voteButton(wrapper: ReactWrapper, value: string): ReactWrapper {
|
||||
|
@ -504,6 +986,22 @@ function votesCount(wrapper: ReactWrapper, value: string): string {
|
|||
).text();
|
||||
}
|
||||
|
||||
function endedVoteChecked(wrapper: ReactWrapper, value: string): boolean {
|
||||
return endedVoteDiv(wrapper, value)
|
||||
.closest(".mx_MPollBody_option")
|
||||
.hasClass("mx_MPollBody_option_checked");
|
||||
}
|
||||
|
||||
function endedVoteDiv(wrapper: ReactWrapper, value: string): ReactWrapper {
|
||||
return wrapper.find(`div[data-value="${value}"]`);
|
||||
}
|
||||
|
||||
function endedVotesCount(wrapper: ReactWrapper, value: string): string {
|
||||
return wrapper.find(
|
||||
`div[data-value="${value}"] .mx_MPollBody_optionVoteCount`,
|
||||
).text();
|
||||
}
|
||||
|
||||
function newPollStart(answers?: IPollAnswer[]): IPollContent {
|
||||
if (!answers) {
|
||||
answers = [
|
||||
|
@ -587,6 +1085,85 @@ function expectedResponseEvent(answer: string) {
|
|||
};
|
||||
}
|
||||
|
||||
function endEvent(
|
||||
sender = "@me:example.com",
|
||||
ts = 0,
|
||||
): MatrixEvent {
|
||||
return new MatrixEvent(
|
||||
{
|
||||
"event_id": nextId(),
|
||||
"room_id": "#myroom:example.com",
|
||||
"origin_server_ts": ts,
|
||||
"type": POLL_END_EVENT_TYPE.name,
|
||||
"sender": sender,
|
||||
"content": {
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.reference",
|
||||
"event_id": "$mypoll",
|
||||
},
|
||||
[POLL_END_EVENT_TYPE.name]: {},
|
||||
[TEXT_NODE_TYPE.name]: "The poll has ended. Something.",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function runIsPollEnded(ends: MatrixEvent[]) {
|
||||
const pollEvent = new MatrixEvent({
|
||||
"event_id": "$mypoll",
|
||||
"room_id": "#myroom:example.com",
|
||||
"content": newPollStart(),
|
||||
});
|
||||
|
||||
const matrixClient = TestUtils.createTestClient();
|
||||
setRedactionAllowedForMeOnly(matrixClient);
|
||||
|
||||
const getRelationsForEvent =
|
||||
(eventId: string, relationType: string, eventType: string) => {
|
||||
expect(eventId).toBe("$mypoll");
|
||||
expect(relationType).toBe("m.reference");
|
||||
expect(eventType).toBe(POLL_END_EVENT_TYPE.name);
|
||||
return newEndRelations(ends);
|
||||
};
|
||||
|
||||
return isPollEnded(pollEvent, matrixClient, getRelationsForEvent);
|
||||
}
|
||||
|
||||
function runFindTopAnswer(votes: MatrixEvent[], ends: MatrixEvent[]) {
|
||||
const pollEvent = new MatrixEvent({
|
||||
"event_id": "$mypoll",
|
||||
"room_id": "#myroom:example.com",
|
||||
"content": newPollStart(),
|
||||
});
|
||||
|
||||
const getRelationsForEvent =
|
||||
(eventId: string, relationType: string, eventType: string) => {
|
||||
expect(eventId).toBe("$mypoll");
|
||||
expect(relationType).toBe("m.reference");
|
||||
if (POLL_RESPONSE_EVENT_TYPE.matches(eventType)) {
|
||||
return newVoteRelations(votes);
|
||||
} else if (POLL_END_EVENT_TYPE.matches(eventType)) {
|
||||
return newEndRelations(ends);
|
||||
} else {
|
||||
fail(`eventType should be end or vote but was ${eventType}`);
|
||||
}
|
||||
};
|
||||
|
||||
return findTopAnswer(pollEvent, MatrixClientPeg.get(), getRelationsForEvent);
|
||||
}
|
||||
|
||||
function setRedactionAllowedForMeOnly(matrixClient: MatrixClient) {
|
||||
matrixClient.getRoom = (_roomId: string) => {
|
||||
return {
|
||||
currentState: {
|
||||
maySendRedactionForEvent: (_evt: MatrixEvent, userId: string) => {
|
||||
return userId === "@me:example.com";
|
||||
},
|
||||
},
|
||||
} as Room;
|
||||
};
|
||||
}
|
||||
|
||||
let EVENT_ID = 0;
|
||||
function nextId(): string {
|
||||
EVENT_ID++;
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue