Update polls UX to match EX Mobile and improve accessibility (#31245)
* Remove poll ended event UI. * Add better aria labels for screen reader and change ui to match mobile UX. - Checkmark and progress bar are only green if the poll is ended. - Updated the Poll icon for open and ended state and added labels - Right align total votes count and update text * Update jest tests * Fix total votes alignment * Fix screenshots * Update snapshot * Update e2e tests * fix more e2e tests * Clean up CSS * Add back text for undisclosed poll (total should be hidden) * Update checkmark and progress colours to more closely match mobile * Don't compute optionNumber on each render * "Total votes" working doesn't really work with the current web behaviour Web doesn't show the votes for undisclosed polls(mobile does). reverting and that behaviour change should be addressed in a different PR(or on mobile.). * Fix e2e test * Update screenshots * Move positioning of total votes label back to the left side as we are no longer changing the copy to match mobile * Don't concatenate label * Fix translation order * Remove unneeded translations * remove O(n^2) code * fix snapshots * Fix check style in poll option * prettier
This commit is contained in:
@@ -505,11 +505,11 @@ describe("MPollBody", () => {
|
||||
expect(runFindTopAnswer([])).toEqual("");
|
||||
});
|
||||
|
||||
it("shows non-radio buttons if the poll is ended", async () => {
|
||||
it("shows disabled radio buttons if the poll is ended", async () => {
|
||||
const events = [newPollEndEvent()];
|
||||
const { container } = await newMPollBody([], events);
|
||||
expect(container.querySelector(".mx_StyledRadioButton")).not.toBeInTheDocument();
|
||||
expect(container.querySelector('input[type="radio"]')).not.toBeInTheDocument();
|
||||
expect(container.querySelector(".mx_StyledRadioButton")).toBeInTheDocument();
|
||||
expect(container.querySelector('input[type="radio"][disabled]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("counts votes as normal if the poll is ended", async () => {
|
||||
@@ -551,8 +551,8 @@ describe("MPollBody", () => {
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(renderResult.container.querySelectorAll('input[type="radio"]')).toHaveLength(0);
|
||||
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(4);
|
||||
expect(renderResult.container.querySelectorAll('input[type="radio"][disabled]')).toHaveLength(4);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
@@ -646,9 +646,9 @@ describe("MPollBody", () => {
|
||||
expect(endedVoteChecked(renderResult, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(renderResult, "pizza")).toBe(false);
|
||||
|
||||
// Double-check by looking for the endedOptionWinner class
|
||||
expect(endedVoteDiv(renderResult, "wings").className.includes("mx_PollOption_endedOptionWinner")).toBe(true);
|
||||
expect(endedVoteDiv(renderResult, "pizza").className.includes("mx_PollOption_endedOptionWinner")).toBe(false);
|
||||
// Double-check by looking for the checked class
|
||||
expect(endedVoteDiv(renderResult, "wings").className.includes("mx_PollOption_checked")).toBe(true);
|
||||
expect(endedVoteDiv(renderResult, "pizza").className.includes("mx_PollOption_checked")).toBe(false);
|
||||
});
|
||||
|
||||
it("highlights multiple winning votes", async () => {
|
||||
@@ -731,9 +731,7 @@ describe("MPollBody", () => {
|
||||
});
|
||||
pollEvent.makeReplaced(replacingEvent);
|
||||
const { getByTestId, container } = await newMPollBodyFromEvent(pollEvent, []);
|
||||
expect(getByTestId("pollQuestion").innerHTML).toEqual(
|
||||
'new question<span class="mx_MPollBody_edited"> (edited)</span>',
|
||||
);
|
||||
expect(getByTestId("pollQuestion").textContent).toEqual("new question (edited)");
|
||||
const inputs = container.querySelectorAll('input[type="radio"]');
|
||||
expect(inputs).toHaveLength(3);
|
||||
expect(inputs[0].getAttribute("value")).toEqual("n1");
|
||||
@@ -951,7 +949,7 @@ function endedVoteChecked({ getByTestId }: RenderResult, value: string): boolean
|
||||
}
|
||||
|
||||
function endedVoteDiv({ getByTestId }: RenderResult, value: string): Element {
|
||||
return getByTestId(`pollOption-${value}`).firstElementChild!;
|
||||
return getByTestId(`pollOption-${value}`);
|
||||
}
|
||||
|
||||
function endedVotesCount(renderResult: RenderResult, value: string): string {
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { type EventTimeline, type MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { type IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
|
||||
import { MPollEndBody } from "../../../../../src/components/views/messages/MPollEndBody";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import {
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
makePollEndEvent,
|
||||
makePollStartEvent,
|
||||
mockClientMethodsEvents,
|
||||
mockClientMethodsUser,
|
||||
setupRoomWithPollEvents,
|
||||
} from "../../../../test-utils";
|
||||
|
||||
describe("<MPollEndBody />", () => {
|
||||
const userId = "@alice:domain.org";
|
||||
const roomId = "!room:domain.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsEvents(),
|
||||
getRoom: jest.fn(),
|
||||
relations: jest.fn(),
|
||||
fetchRoomEvent: jest.fn(),
|
||||
});
|
||||
const pollStartEvent = makePollStartEvent("Question?", userId, undefined, { roomId });
|
||||
const pollEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
|
||||
|
||||
const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise<Room> => {
|
||||
if (pollStart) {
|
||||
await setupRoomWithPollEvents([pollStart], [], [pollEnd], mockClient);
|
||||
}
|
||||
const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId);
|
||||
|
||||
// end events validate against this
|
||||
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation(
|
||||
(_evt: MatrixEvent, id: string) => {
|
||||
return id === mockClient.getSafeUserId();
|
||||
},
|
||||
);
|
||||
|
||||
const timelineSet = room.getUnfilteredTimelineSet();
|
||||
const getTimelineForEventSpy = jest.spyOn(timelineSet, "getTimelineForEvent");
|
||||
// if we have a pollStart, mock the room timeline to include it
|
||||
if (pollStart) {
|
||||
const eventTimeline = {
|
||||
getEvents: jest.fn().mockReturnValue([pollEnd, pollStart]),
|
||||
} as unknown as EventTimeline;
|
||||
getTimelineForEventSpy.mockReturnValue(eventTimeline);
|
||||
}
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
|
||||
return room;
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
mxEvent: pollEndEvent,
|
||||
highlightLink: "unused",
|
||||
mediaEventHelper: {} as unknown as MediaEventHelper,
|
||||
onMessageAllowed: () => {},
|
||||
permalinkCreator: {} as unknown as RoomPermalinkCreator,
|
||||
ref: undefined as any,
|
||||
};
|
||||
|
||||
const getComponent = (props: Partial<IBodyProps> = {}) =>
|
||||
render(<MPollEndBody {...defaultProps} {...props} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.getRoom.mockReset();
|
||||
mockClient.relations.mockResolvedValue({
|
||||
events: [],
|
||||
});
|
||||
mockClient.fetchRoomEvent.mockResolvedValue(pollStartEvent.getEffectiveEvent());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
});
|
||||
|
||||
describe("when poll start event exists in current timeline", () => {
|
||||
it("renders an ended poll", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent, pollStartEvent);
|
||||
const { container } = getComponent();
|
||||
|
||||
// ended poll rendered
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// didnt try to fetch start event while it was already in timeline
|
||||
expect(mockClient.fetchRoomEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not render a poll tile when end event is invalid", async () => {
|
||||
// sender of end event does not match start event
|
||||
const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
|
||||
await setupRoomWithEventsTimeline(invalidEndEvent, pollStartEvent);
|
||||
const { getByText } = getComponent({ mxEvent: invalidEndEvent });
|
||||
|
||||
// no poll tile rendered
|
||||
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when poll start event does not exist in current timeline", () => {
|
||||
it("fetches the related poll start event and displays a poll tile", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
const { container, getByTestId, getByRole, queryByRole } = getComponent();
|
||||
|
||||
// while fetching event, only icon is shown
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByRole("progressbar")).not.toBeInTheDocument());
|
||||
|
||||
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
|
||||
|
||||
// quick check for poll tile
|
||||
expect(getByTestId("pollQuestion").innerHTML).toEqual("Question?");
|
||||
expect(getByTestId("totalVotes").innerHTML).toEqual("Final result based on 0 votes");
|
||||
});
|
||||
|
||||
it("does not render a poll tile when end event is invalid", async () => {
|
||||
// sender of end event does not match start event
|
||||
const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
|
||||
await setupRoomWithEventsTimeline(invalidEndEvent);
|
||||
const { getByText } = getComponent({ mxEvent: invalidEndEvent });
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
|
||||
// no poll tile rendered
|
||||
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("logs an error and displays the text fallback when fetching the start event fails", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
const { getByText } = getComponent();
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
|
||||
// poll end event fallback text used
|
||||
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||
expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
|
||||
});
|
||||
|
||||
it("logs an error and displays the extensible event text when fetching the start event fails", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
const { getByText } = getComponent();
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
|
||||
// poll end event fallback text used
|
||||
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||
expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
|
||||
});
|
||||
|
||||
it("displays fallback text when the poll end event does not have text", async () => {
|
||||
const endWithoutText = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
|
||||
delete endWithoutText.getContent()[M_TEXT.name];
|
||||
await setupRoomWithEventsTimeline(endWithoutText);
|
||||
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||
const { getByText } = getComponent({ mxEvent: endWithoutText });
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
|
||||
// default fallback text used
|
||||
expect(getByText("@alice:domain.org has ended a poll")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,129 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<MPollEndBody /> when poll start event does not exist in current timeline fetches the related poll start event and displays a poll tile 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="mx_MPollEndBody_icon"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M21 10.659V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21H5q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h8.341A6 6 0 0 0 13 5H5v14h14v-8a6 6 0 0 0 2-.341"
|
||||
/>
|
||||
<path
|
||||
d="M13.803 8a6 6 0 0 0 1.88 2H13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 9q0-.424.287-.713A.97.97 0 0 1 13 8zm2.91 7.713A.97.97 0 0 1 16 16h-3a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 15q0-.424.287-.713A.97.97 0 0 1 13 14h3q.424 0 .712.287.288.288.288.713 0 .424-.288.713m-6.299-5.3A1.93 1.93 0 0 1 9 11q-.825 0-1.412-.588A1.93 1.93 0 0 1 7 9q0-.825.588-1.412A1.93 1.93 0 0 1 9 7q.825 0 1.412.588Q11 8.175 11 9t-.588 1.412m.001 6.001A1.93 1.93 0 0 1 9 17q-.825 0-1.412-.587A1.93 1.93 0 0 1 7 15q0-.825.588-1.412A1.93 1.93 0 0 1 9 13q.825 0 1.412.588Q11 14.175 11 15q0 .824-.588 1.413m12.295-14.12a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L18 5.586l3.293-3.293a1 1 0 0 1 1.414 0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MPollEndBody /> when poll start event exists in current timeline renders an ended poll 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MPollEndBody"
|
||||
>
|
||||
<span
|
||||
class="mx_Caption"
|
||||
>
|
||||
Ended a poll
|
||||
</span>
|
||||
<fieldset
|
||||
class="mx_MPollBody"
|
||||
>
|
||||
<legend
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
Question?
|
||||
</legend>
|
||||
<div
|
||||
class="mx_MPollBody_allOptions"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption mx_PollOption_ended"
|
||||
data-testid="pollOption-socks"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_endedOption"
|
||||
data-value="socks"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_optionText"
|
||||
>
|
||||
Socks
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_popularityBackground"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_popularityAmount"
|
||||
style="width: 0%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption mx_PollOption_ended"
|
||||
data-testid="pollOption-shoes"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_endedOption"
|
||||
data-value="shoes"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_optionText"
|
||||
>
|
||||
Shoes
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollOption_popularityBackground"
|
||||
>
|
||||
<div
|
||||
class="mx_PollOption_popularityAmount"
|
||||
style="width: 0%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_totalVotes"
|
||||
data-testid="totalVotes"
|
||||
>
|
||||
Final result based on 0 votes
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 16px; height: 16px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -158,4 +158,53 @@ describe("<PollListItemEnded />", () => {
|
||||
expect(getByText("Nissan Silvia S15")).toBeInTheDocument();
|
||||
expect(queryByText("Mitsubishi Lancer Evolution IX")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("maintains correct option numbers when only later answers win", async () => {
|
||||
// Create a poll with 3 answers
|
||||
const answerThree = {
|
||||
id: "answerThreeId",
|
||||
[M_TEXT.name]: "Toyota Supra MK4",
|
||||
};
|
||||
const pollStartEventThreeAnswers = makePollStartEvent(
|
||||
"Question?",
|
||||
userId,
|
||||
[answerOne, answerTwo, answerThree],
|
||||
{
|
||||
roomId,
|
||||
id: pollId,
|
||||
ts: timestamp,
|
||||
},
|
||||
);
|
||||
|
||||
// Only answer 3 (index 2) wins with 2 votes, answers 1 and 2 (indices 0 and 1) get 0 or 1 votes
|
||||
const responses = [
|
||||
makePollResponseEvent(pollId, [answerOne.id], userId, roomId, timestamp + 1),
|
||||
makePollResponseEvent(pollId, [answerThree.id], "@bob:domain.org", roomId, timestamp + 1),
|
||||
makePollResponseEvent(pollId, [answerThree.id], "@charlie:domain.org", roomId, timestamp + 1),
|
||||
];
|
||||
|
||||
await setupRoomWithPollEvents([pollStartEventThreeAnswers], responses, [pollEndEvent], mockClient, room);
|
||||
const poll = room.polls.get(pollId)!;
|
||||
|
||||
const { getByText, queryByText, findByText, getByTestId } = getComponent({
|
||||
event: pollStartEventThreeAnswers,
|
||||
poll,
|
||||
});
|
||||
|
||||
await expect(findByText("Final result based on 3 votes")).resolves.toBeInTheDocument();
|
||||
|
||||
// Only the third answer should be shown (it won)
|
||||
expect(queryByText("Nissan Silvia S15")).not.toBeInTheDocument();
|
||||
expect(queryByText("Mitsubishi Lancer Evolution IX")).not.toBeInTheDocument();
|
||||
expect(getByText("Toyota Supra MK4")).toBeInTheDocument();
|
||||
|
||||
// The option number should be 3 (original index 2 + 1), not 1
|
||||
// PollOption component receives optionNumber prop which is used in its aria-label
|
||||
const pollOption = getByTestId("pollOption-answerThreeId");
|
||||
expect(pollOption).toBeInTheDocument();
|
||||
|
||||
// The optionNumber is maintained correctly
|
||||
const radioInput = pollOption.querySelector('input[type="radio"]');
|
||||
expect(radioInput).toHaveAttribute("aria-label", expect.stringContaining("Option 3, Toyota Supra MK4"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,18 @@ exports[`<PollHistory /> Poll detail displays poll detail on active poll list it
|
||||
<legend
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
<svg
|
||||
aria-label="Poll"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M16 10q.424 0 .712-.287A.97.97 0 0 0 17 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 8h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 9q0 .424.287.713.288.287.713.287zm0 6q.424 0 .712-.287A.97.97 0 0 0 17 15a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 14h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 15q0 .424.287.713.288.287.713.287zm-7-5q.825 0 1.412-.588Q11 9.826 11 9t-.588-1.412A1.93 1.93 0 0 0 9 7q-.825 0-1.412.588A1.93 1.93 0 0 0 7 9q0 .825.588 1.412Q8.175 11 9 11m0 6q.825 0 1.412-.587Q11 15.825 11 15t-.588-1.412A1.93 1.93 0 0 0 9 13q-.825 0-1.412.588A1.93 1.93 0 0 0 7 15q0 .824.588 1.413Q8.175 17 9 17m-4 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h14q.824 0 1.413.587Q21 4.176 21 5v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm0-2h14V5H5z"
|
||||
/>
|
||||
</svg>
|
||||
Question?
|
||||
</legend>
|
||||
`;
|
||||
@@ -12,6 +24,21 @@ exports[`<PollHistory /> Poll detail displays poll detail on past poll list item
|
||||
<legend
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
<svg
|
||||
aria-label="Poll ended"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M21 10.659V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21H5q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h8.341A6 6 0 0 0 13 5H5v14h14v-8a6 6 0 0 0 2-.341"
|
||||
/>
|
||||
<path
|
||||
d="M13.803 8a6 6 0 0 0 1.88 2H13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 9q0-.424.287-.713A.97.97 0 0 1 13 8zm2.91 7.713A.97.97 0 0 1 16 16h-3a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 15q0-.424.287-.713A.97.97 0 0 1 13 14h3q.424 0 .712.287.288.288.288.713 0 .424-.288.713m-6.299-5.3A1.93 1.93 0 0 1 9 11q-.825 0-1.412-.588A1.93 1.93 0 0 1 7 9q0-.825.588-1.412A1.93 1.93 0 0 1 9 7q.825 0 1.412.588Q11 8.175 11 9t-.588 1.412m.001 6.001A1.93 1.93 0 0 1 9 17q-.825 0-1.412-.587A1.93 1.93 0 0 1 7 15q0-.825.588-1.412A1.93 1.93 0 0 1 9 13q.825 0 1.412.588Q11 14.175 11 15q0 .824-.588 1.413m12.295-14.12a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L18 5.586l3.293-3.293a1 1 0 0 1 1.414 0"
|
||||
/>
|
||||
</svg>
|
||||
What?
|
||||
</legend>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user