Allow ending polls (#7305)

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Andy Balaam 2021-12-08 14:56:48 +00:00 committed by GitHub
parent 697b5d28b3
commit 2b52e17a80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 2814 additions and 680 deletions

View file

@ -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++;