/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fireEvent, render, RenderResult } from "jest-matrix-react";
import {
Room,
MatrixEvent,
M_POLL_KIND_DISCLOSED,
M_POLL_KIND_UNDISCLOSED,
M_POLL_START,
M_TEXT,
} from "matrix-js-sdk/src/matrix";
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { ReplacementEvent } from "matrix-js-sdk/src/types";
import { getMockClientWithEventEmitter } from "../../../../test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import PollCreateDialog from "../../../../../src/components/views/elements/PollCreateDialog";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
// Fake date to give a predictable snapshot
const realDateNow = Date.now;
const realDateToISOString = Date.prototype.toISOString;
Date.now = jest.fn(() => 2345678901234);
// eslint-disable-next-line no-extend-native
Date.prototype.toISOString = jest.fn(() => "2021-11-23T14:35:14.240Z");
afterAll(() => {
Date.now = realDateNow;
// eslint-disable-next-line no-extend-native
Date.prototype.toISOString = realDateToISOString;
});
describe("PollCreateDialog", () => {
const mockClient = getMockClientWithEventEmitter({
sendEvent: jest.fn().mockResolvedValue({ event_id: "1" }),
});
beforeEach(() => {
mockClient.sendEvent.mockClear();
});
it("renders a blank poll", () => {
const dialog = render(
,
);
expect(dialog.asFragment()).toMatchSnapshot();
});
it("autofocuses the poll topic on mount", () => {
const dialog = render();
expect(dialog.container.querySelector("#poll-topic-input")).toHaveFocus();
});
it("autofocuses the new poll option field after clicking add option button", () => {
const dialog = render();
expect(dialog.container.querySelector("#poll-topic-input")).toHaveFocus();
fireEvent.click(dialog.container.querySelector("div.mx_PollCreateDialog_addOption")!);
expect(dialog.container.querySelector("#poll-topic-input")).not.toHaveFocus();
expect(dialog.container.querySelector("#pollcreate_option_1")).not.toHaveFocus();
expect(dialog.container.querySelector("#pollcreate_option_2")).toHaveFocus();
});
it("renders a question and some options", () => {
const dialog = render();
expectSubmitToBeDisabled(dialog, true);
// When I set some values in the boxes
changeValue(dialog, "Question or topic", "How many turnips is the optimal number?");
changeValue(dialog, "Option 1", "As many as my neighbour");
changeValue(dialog, "Option 2", "The question is meaningless");
fireEvent.click(dialog.container.querySelector("div.mx_PollCreateDialog_addOption")!);
changeValue(dialog, "Option 3", "Mu");
expect(dialog.asFragment()).toMatchSnapshot();
});
it("renders info from a previous event", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
const dialog = render(
,
);
expectSubmitToBeDisabled(dialog, false);
expect(dialog.asFragment()).toMatchSnapshot();
});
it("doesn't allow submitting until there are options", () => {
const dialog = render();
expectSubmitToBeDisabled(dialog, true);
});
it("does allow submitting when there are options and a question", () => {
// Given a dialog with no info in (which I am unable to submit)
const dialog = render();
expectSubmitToBeDisabled(dialog, true);
// When I set some values in the boxes
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
// Then I am able to submit
expectSubmitToBeDisabled(dialog, false);
});
it("shows the open poll description at first", () => {
const dialog = render();
expect(dialog.container.querySelector("select")).toHaveValue(M_POLL_KIND_DISCLOSED.name);
expect(dialog.container.querySelector("p")).toHaveTextContent("Voters see results as soon as they have voted");
});
it("shows the closed poll description if we choose it", () => {
const dialog = render();
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.container.querySelector("select")).toHaveValue(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.container.querySelector("p")).toHaveTextContent(
"Results are only revealed when you end the poll",
);
});
it("shows the open poll description if we choose it", () => {
const dialog = render();
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
changeKind(dialog, M_POLL_KIND_DISCLOSED.name);
expect(dialog.container.querySelector("select")).toHaveValue(M_POLL_KIND_DISCLOSED.name);
expect(dialog.container.querySelector("p")).toHaveTextContent("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 = render(
,
);
expect(dialog.container.querySelector("select")).toHaveValue(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.container.querySelector("p")).toHaveTextContent(
"Results are only revealed when you end the poll",
);
});
it("displays a spinner after submitting", () => {
const dialog = render();
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
expect(dialog.container.querySelector(".mx_Spinner")).toBeFalsy();
fireEvent.click(dialog.container.querySelector("button")!);
expect(dialog.container.querySelector(".mx_Spinner")).toBeDefined();
});
it("sends a poll create event when submitted", () => {
const dialog = render();
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
fireEvent.click(dialog.container.querySelector("button")!);
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual({
[M_TEXT.name]: "Q\n1. A1\n2. A2",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "A1",
},
{
id: expect.any(String),
[M_TEXT.name]: "A2",
},
],
kind: M_POLL_KIND_DISCLOSED.name,
max_selections: 1,
question: {
body: "Q",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Q",
},
},
});
});
it("sends a poll edit event when editing", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = render(
,
);
changeValue(dialog, "Question or topic", "Poll Q updated");
changeValue(dialog, "Option 2", "Answer 2 updated");
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
fireEvent.click(dialog.container.querySelector("button")!);
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual({
"m.new_content": {
[M_TEXT.name]: "Poll Q updated\n1. Answer 1\n2. Answer 2 updated",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "Answer 1",
},
{
id: expect.any(String),
[M_TEXT.name]: "Answer 2 updated",
},
],
kind: M_POLL_KIND_UNDISCLOSED.name,
max_selections: 1,
question: {
body: "Poll Q updated",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Poll Q updated",
},
},
},
"m.relates_to": {
event_id: previousEvent.getId(),
rel_type: "m.replace",
},
});
});
it("retains poll disclosure type when editing", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = render(
,
);
changeValue(dialog, "Question or topic", "Poll Q updated");
fireEvent.click(dialog.container.querySelector("button")!);
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
// didnt change
expect((sentEventContent as ReplacementEvent)["m.new_content"][M_POLL_START.name].kind).toEqual(
M_POLL_KIND_DISCLOSED.name,
);
});
});
function createRoom(): Room {
return new Room("roomid", MatrixClientPeg.safeGet(), "@name:example.com", {});
}
function changeValue(wrapper: RenderResult, labelText: string, value: string) {
fireEvent.change(wrapper.container.querySelector(`input[label="${labelText}"]`)!, {
target: { value: value },
});
}
function changeKind(wrapper: RenderResult, value: string) {
fireEvent.change(wrapper.container.querySelector("select")!, { target: { value: value } });
}
function expectSubmitToBeDisabled(wrapper: RenderResult, disabled: boolean) {
if (disabled) {
expect(wrapper.container.querySelector('button[type="submit"]')).toHaveAttribute("aria-disabled", "true");
} else {
expect(wrapper.container.querySelector('button[type="submit"]')).not.toHaveAttribute("aria-disabled", "true");
}
}