/* 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"); } }