Poll history: detail screen (#10172)

* basic navigation to focused poll

* add tooltip

* drill permalinkCreator down to poll history

* render poll tile and link to timeline

* tidy and lint

* unit test poll detail

* add view poll link to ended pollliste item

* strict fix

* pr improvements

* pass room as prop

* permalinkcreator ts assertion
This commit is contained in:
Kerry 2023-02-28 09:39:55 +13:00 committed by GitHub
parent 9b2b3ca42e
commit f57495d3cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 588 additions and 104 deletions

View file

@ -31,6 +31,9 @@ import {
setupRoomWithPollEvents,
unmockIntlDateTimeFormat,
} from "../../../../test-utils";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../../src/dispatcher/actions";
describe("<PollHistoryDialog />", () => {
// 14.03.2022 16:15
@ -57,8 +60,9 @@ describe("<PollHistoryDialog />", () => {
});
const defaultProps = {
roomId,
room,
matrixClient: mockClient,
permalinkCreator: new RoomPermalinkCreator(room),
onFinished: jest.fn(),
};
const getComponent = () => render(<PollHistoryDialog {...defaultProps} />);
@ -70,9 +74,12 @@ describe("<PollHistoryDialog />", () => {
beforeEach(() => {
room = new Room(roomId, mockClient, userId);
mockClient.getRoom.mockReturnValue(room);
defaultProps.room = room;
mockClient.relations.mockResolvedValue({ events: [] });
const timeline = room.getLiveTimeline();
jest.spyOn(timeline, "getEvents").mockReturnValue([]);
jest.spyOn(defaultDispatcher, "dispatch").mockClear();
defaultProps.onFinished.mockClear();
jest.spyOn(room, "getOrCreateFilteredTimelineSet");
mockClient.getOrCreateFilter.mockResolvedValue(expectedFilter.filterId!);
mockClient.paginateEventTimeline.mockReset().mockResolvedValue(false);
@ -314,4 +321,165 @@ describe("<PollHistoryDialog />", () => {
// this poll is ended
expect(getByText("What?")).toBeInTheDocument();
});
describe("Poll detail", () => {
const timestamp = 1675300825090;
const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: timestamp, id: "$1" });
const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: timestamp + 10000, id: "$2" });
const pollStart3 = makePollStartEvent("What?", userId, undefined, { ts: timestamp + 70000, id: "$3" });
const pollEnd3 = makePollEndEvent(pollStart3.getId()!, roomId, userId, timestamp + 1, "$4");
it("displays poll detail on active poll list item click", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText, queryByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
expect(queryByText("Polls history")).not.toBeInTheDocument();
// elements from MPollBody
expect(getByText("Question?")).toMatchSnapshot();
expect(getByText("Socks")).toBeInTheDocument();
expect(getByText("Shoes")).toBeInTheDocument();
});
it("links to the poll start event from an active poll detail", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
// links to poll start event
expect(getByText("View poll in timeline").getAttribute("href")).toBe(
`https://matrix.to/#/!room:domain.org/${pollStart1.getId()!}`,
);
});
it("navigates in app when clicking view in timeline button", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
const event = new MouseEvent("click", { bubbles: true, cancelable: true });
jest.spyOn(event, "preventDefault");
fireEvent(getByText("View poll in timeline"), event);
expect(event.preventDefault).toHaveBeenCalled();
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.ViewRoom,
event_id: pollStart1.getId()!,
highlighted: true,
metricsTrigger: undefined,
room_id: pollStart1.getRoomId()!,
});
// dialog closed
expect(defaultProps.onFinished).toHaveBeenCalled();
});
it("doesnt navigate in app when view in timeline link is ctrl + clicked", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
const event = new MouseEvent("click", { bubbles: true, cancelable: true, ctrlKey: true });
jest.spyOn(event, "preventDefault");
fireEvent(getByText("View poll in timeline"), event);
expect(event.preventDefault).not.toHaveBeenCalled();
expect(defaultDispatcher.dispatch).not.toHaveBeenCalled();
expect(defaultProps.onFinished).not.toHaveBeenCalled();
});
it("navigates back to poll list from detail view on header click", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText, queryByText, getByTestId, container } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
// detail view
expect(getByText("Question?")).toBeInTheDocument();
// header not shown
expect(queryByText("Polls history")).not.toBeInTheDocument();
expect(getByText("Active polls")).toMatchSnapshot();
fireEvent.click(getByText("Active polls"));
// main list header displayed again
expect(getByText("Polls history")).toBeInTheDocument();
// active filter still active
expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked();
// list displayed
expect(container.getElementsByClassName("mx_PollHistoryList_list").length).toBeTruthy();
});
it("displays poll detail on past poll list item click", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Past polls"));
// pollStart3 is ended
fireEvent.click(getByText("What?"));
expect(getByText("What?")).toMatchSnapshot();
});
it("links to the poll end events from a ended poll detail", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText } = getComponent();
await flushPromises();
fireEvent.click(getByText("Past polls"));
// pollStart3 is ended
fireEvent.click(getByText("What?"));
// links to poll end event
expect(getByText("View poll in timeline").getAttribute("href")).toBe(
`https://matrix.to/#/!room:domain.org/${pollEnd3.getId()!}`,
);
});
it("navigates back to poll list from detail view on header click", async () => {
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
const { getByText, queryByText, getByTestId, container } = getComponent();
await flushPromises();
fireEvent.click(getByText("Question?"));
// detail view
expect(getByText("Question?")).toBeInTheDocument();
// header not shown
expect(queryByText("Polls history")).not.toBeInTheDocument();
expect(getByText("Active polls")).toMatchSnapshot();
fireEvent.click(getByText("Active polls"));
// main list header displayed again
expect(getByText("Polls history")).toBeInTheDocument();
// active filter still active
expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked();
// list displayed
expect(container.getElementsByClassName("mx_PollHistoryList_list").length).toBeTruthy();
});
});
});

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { render } from "@testing-library/react";
import { fireEvent, render } from "@testing-library/react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { PollListItem } from "../../../../../src/components/views/dialogs/polls/PollListItem";
@ -24,7 +24,7 @@ import { makePollStartEvent, mockIntlDateTimeFormat, unmockIntlDateTimeFormat }
describe("<PollListItem />", () => {
const event = makePollStartEvent("Question?", "@me:domain.org");
event.getContent().origin;
const defaultProps = { event };
const defaultProps = { event, onClick: jest.fn() };
const getComponent = (props = {}) => render(<PollListItem {...defaultProps} {...props} />);
beforeAll(() => {
@ -50,4 +50,13 @@ describe("<PollListItem />", () => {
const { container } = getComponent({ event });
expect(container.firstElementChild).toBeFalsy();
});
it("calls onClick handler on click", () => {
const onClick = jest.fn();
const { getByText } = getComponent({ onClick });
fireEvent.click(getByText("Question?"));
expect(onClick).toHaveBeenCalled();
});
});

View file

@ -60,7 +60,8 @@ describe("<PollListItemEnded />", () => {
});
const pollEndEvent = makePollEndEvent(pollId, roomId, userId, timestamp + 60000);
const getComponent = (props: { event: MatrixEvent; poll: Poll }) => render(<PollListItemEnded {...props} />);
const getComponent = (props: { event: MatrixEvent; poll: Poll }) =>
render(<PollListItemEnded {...props} onClick={jest.fn()} />);
beforeAll(() => {
// mock default locale to en-GB and set timezone

View file

@ -1,5 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PollHistoryDialog /> Poll detail displays poll detail on active poll list item click 1`] = `
<h2
data-testid="pollQuestion"
>
Question?
</h2>
`;
exports[`<PollHistoryDialog /> Poll detail displays poll detail on past poll list item click 1`] = `
<h2
data-testid="pollQuestion"
>
What?
</h2>
`;
exports[`<PollHistoryDialog /> Poll detail navigates back to poll list from detail view on header click 1`] = `
<div
class="mx_AccessibleButton mx_PollDetailHeader mx_AccessibleButton_hasKind mx_AccessibleButton_kind_content_inline"
role="button"
tabindex="0"
>
<div
class="mx_PollDetailHeader_icon"
/>
Active polls
</div>
`;
exports[`<PollHistoryDialog /> Poll detail navigates back to poll list from detail view on header click 2`] = `
<div
class="mx_AccessibleButton mx_PollDetailHeader mx_AccessibleButton_hasKind mx_AccessibleButton_kind_content_inline"
role="button"
tabindex="0"
>
<div
class="mx_PollDetailHeader_icon"
/>
Active polls
</div>
`;
exports[`<PollHistoryDialog /> renders a list of active polls when there are polls in the room 1`] = `
<div>
<div
@ -71,33 +113,49 @@ exports[`<PollHistoryDialog /> renders a list of active polls when there are pol
class="mx_PollListItem"
data-testid="pollListItem-$2"
>
<span>
02/02/23
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
tabindex="0"
>
Where?
</span>
<div
class="mx_PollListItem_content"
>
<span>
02/02/23
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
>
Where?
</span>
</div>
</div>
</li>
<li
class="mx_PollListItem"
data-testid="pollListItem-$1"
>
<span>
02/02/23
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
tabindex="0"
>
Question?
</span>
<div
class="mx_PollListItem_content"
>
<span>
02/02/23
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
>
Question?
</span>
</div>
</div>
</li>
</ol>
</div>

View file

@ -6,17 +6,25 @@ exports[`<PollListItem /> renders a poll 1`] = `
class="mx_PollListItem"
data-testid="pollListItem-$mypoll"
>
<span>
01/01/70
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
tabindex="0"
>
Question?
</span>
<div
class="mx_PollListItem_content"
>
<span>
01/01/70
</span>
<div
class="mx_PollListItem_icon"
/>
<span
class="mx_PollListItem_question"
>
Question?
</span>
</div>
</div>
</li>
</div>
`;

View file

@ -7,30 +7,38 @@ exports[`<PollListItemEnded /> renders a poll with no responses 1`] = `
data-testid="pollListItem-1"
>
<div
class="mx_PollListItemEnded_title"
tabindex="0"
>
<div
class="mx_PollListItemEnded_icon"
/>
<span
class="mx_PollListItemEnded_question"
class="mx_PollListItemEnded_content"
>
Question?
</span>
<span
class="mx_Caption"
>
02/02/23
</span>
</div>
<div
class="mx_PollListItemEnded_voteCount"
>
<span
class="mx_Caption"
>
Final result based on 0 votes
</span>
<div
class="mx_PollListItemEnded_title"
>
<div
class="mx_PollListItemEnded_icon"
/>
<span
class="mx_PollListItemEnded_question"
>
Question?
</span>
<span
class="mx_Caption"
>
02/02/23
</span>
</div>
<div
class="mx_PollListItemEnded_voteCount"
>
<span
class="mx_Caption"
>
Final result based on 0 votes
</span>
</div>
</div>
</div>
</li>
</div>

View file

@ -30,6 +30,7 @@ import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
import { PollHistoryDialog } from "../../../../src/components/views/dialogs/polls/PollHistoryDialog";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
describe("<RoomSummaryCard />", () => {
const userId = "@alice:domain.org";
@ -54,6 +55,7 @@ describe("<RoomSummaryCard />", () => {
const defaultProps = {
room,
onClose: jest.fn(),
permalinkCreator: new RoomPermalinkCreator(room),
};
const getComponent = (props = {}) =>
render(<RoomSummaryCard {...defaultProps} {...props} />, {
@ -145,7 +147,11 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("Polls history"));
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { roomId, matrixClient: mockClient });
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, {
room,
matrixClient: mockClient,
permalinkCreator: defaultProps.permalinkCreator,
});
});
});

View file

@ -66,9 +66,15 @@ export const makePollStartEvent = (
});
};
export const makePollEndEvent = (pollStartEventId: string, roomId: string, sender: string, ts = 0): MatrixEvent => {
export const makePollEndEvent = (
pollStartEventId: string,
roomId: string,
sender: string,
ts = 0,
id?: string,
): MatrixEvent => {
return new MatrixEvent({
event_id: uuid4(),
event_id: id || uuid4(),
room_id: roomId,
origin_server_ts: ts,
type: M_POLL_END.name,