Merge branch 'develop' into johannes/find-myself
This commit is contained in:
commit
f842e319de
26 changed files with 707 additions and 271 deletions
|
@ -69,10 +69,10 @@ describe("<PollHistoryDialog />", () => {
|
|||
expect(getByText("There are no polls in this room")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders a list of polls when there are polls in the timeline", () => {
|
||||
const pollStart1 = makePollStartEvent("Question?", userId, undefined, 1675300825090, "$1");
|
||||
const pollStart2 = makePollStartEvent("Where?", userId, undefined, 1675300725090, "$2");
|
||||
const pollStart3 = makePollStartEvent("What?", userId, undefined, 1675200725090, "$3");
|
||||
it("renders a list of polls when there are polls in the timeline", async () => {
|
||||
const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: 1675300825090, id: "$1" });
|
||||
const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: 1675300725090, id: "$2" });
|
||||
const pollStart3 = makePollStartEvent("What?", userId, undefined, { ts: 1675200725090, id: "$3" });
|
||||
const message = new MatrixEvent({
|
||||
type: "m.room.message",
|
||||
content: {},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,13 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
// eslint-disable-next-line deprecate/import
|
||||
import { mount } from "enzyme";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import ZoomButtons from "../../../../src/components/views/location/ZoomButtons";
|
||||
import { findByTestId } from "../../../test-utils";
|
||||
|
||||
describe("<ZoomButtons />", () => {
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
|
@ -29,7 +26,7 @@ describe("<ZoomButtons />", () => {
|
|||
const defaultProps = {
|
||||
map: mockMap,
|
||||
};
|
||||
const getComponent = (props = {}) => mount(<ZoomButtons {...defaultProps} {...props} />);
|
||||
const getComponent = (props = {}) => render(<ZoomButtons {...defaultProps} {...props} />);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -37,15 +34,12 @@ describe("<ZoomButtons />", () => {
|
|||
|
||||
it("renders buttons", () => {
|
||||
const component = getComponent();
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls map zoom in on zoom in click", () => {
|
||||
const component = getComponent();
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, "map-zoom-in-button").at(0).simulate("click");
|
||||
});
|
||||
screen.getByTestId("map-zoom-in-button").click();
|
||||
|
||||
expect(mockMap.zoomIn).toHaveBeenCalled();
|
||||
expect(component).toBeTruthy();
|
||||
|
@ -53,10 +47,7 @@ describe("<ZoomButtons />", () => {
|
|||
|
||||
it("calls map zoom out on zoom out click", () => {
|
||||
const component = getComponent();
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, "map-zoom-out-button").at(0).simulate("click");
|
||||
});
|
||||
screen.getByTestId("map-zoom-out-button").click();
|
||||
|
||||
expect(mockMap.zoomOut).toHaveBeenCalled();
|
||||
expect(component).toBeTruthy();
|
||||
|
|
|
@ -162,7 +162,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
|||
>
|
||||
<AccessibleButton
|
||||
className="mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-in-button"
|
||||
data-testid="map-zoom-in-button"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
|
@ -171,7 +171,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
|||
>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-in-button"
|
||||
data-testid="map-zoom-in-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
|
@ -186,7 +186,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
|||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-out-button"
|
||||
data-testid="map-zoom-out-button"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
|
@ -195,7 +195,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
|||
>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-out-button"
|
||||
data-testid="map-zoom-out-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
|
|
|
@ -1,74 +1,32 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ZoomButtons /> renders buttons 1`] = `
|
||||
<ZoomButtons
|
||||
map={
|
||||
MockMap {
|
||||
"_events": {},
|
||||
"_eventsCount": 0,
|
||||
"_maxListeners": undefined,
|
||||
"addControl": [MockFunction],
|
||||
"fitBounds": [MockFunction],
|
||||
"removeControl": [MockFunction],
|
||||
"setCenter": [MockFunction],
|
||||
"setStyle": [MockFunction],
|
||||
"zoomIn": [MockFunction],
|
||||
"zoomOut": [MockFunction],
|
||||
Symbol(kCapture): false,
|
||||
}
|
||||
}
|
||||
>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="mx_ZoomButtons"
|
||||
class="mx_ZoomButtons"
|
||||
>
|
||||
<AccessibleButton
|
||||
className="mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-in-button"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-in-button"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
tabindex="0"
|
||||
title="Zoom in"
|
||||
>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-in-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Zoom in"
|
||||
>
|
||||
<div
|
||||
className="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-out-button"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-out-button"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
tabindex="0"
|
||||
title="Zoom out"
|
||||
>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-test-id="map-zoom-out-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Zoom out"
|
||||
>
|
||||
<div
|
||||
className="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ZoomButtons>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -16,10 +16,9 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import {
|
||||
M_POLL_END,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_KIND_UNDISCLOSED,
|
||||
M_POLL_RESPONSE,
|
||||
|
@ -31,7 +30,13 @@ import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
|||
|
||||
import { allVotes, findTopAnswer, isPollEnded } from "../../../../src/components/views/messages/MPollBody";
|
||||
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
||||
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
|
||||
import {
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
makePollEndEvent,
|
||||
mockClientMethodsUser,
|
||||
setupRoomWithPollEvents,
|
||||
} from "../../../test-utils";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import MPollBody from "../../../../src/components/views/messages/MPollBody";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
|
@ -112,7 +117,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const ends = [endEvent("@notallowed:example.com", 12)];
|
||||
const ends = [newPollEndEvent("@notallowed:example.com", 12)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
// Even though an end event was sent, we render the poll as unfinished
|
||||
|
@ -222,7 +227,7 @@ describe("MPollBody", () => {
|
|||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
// wait for /relations promise to resolve
|
||||
await flushPromises();
|
||||
|
@ -250,7 +255,7 @@ describe("MPollBody", () => {
|
|||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
// wait for /relations promise to resolve
|
||||
await flushPromises();
|
||||
|
@ -422,7 +427,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 12)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 12)];
|
||||
const renderResult = await newMPollBody(votes, ends, undefined, false);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("3 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
|
@ -471,7 +476,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("sends no events when I click in an ended poll", async () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const votes = [responseEvent("@uy:example.com", "wings", 15), responseEvent("@uy:example.com", "poutine", 15)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
clickOption(renderResult, "wings");
|
||||
|
@ -509,7 +514,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("shows non-radio buttons if the poll is ended", async () => {
|
||||
const events = [endEvent()];
|
||||
const events = [newPollEndEvent()];
|
||||
const { container } = await newMPollBody([], events);
|
||||
expect(container.querySelector(".mx_StyledRadioButton")).not.toBeInTheDocument();
|
||||
expect(container.querySelector('input[type="radio"]')).not.toBeInTheDocument();
|
||||
|
@ -523,7 +528,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
|
||||
responseEvent("@qbert:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
|
@ -534,7 +539,7 @@ describe("MPollBody", () => {
|
|||
|
||||
it("counts a single vote as normal if the poll is ended", async () => {
|
||||
const votes = [responseEvent("@qbert:example.com", "poutine", 16)];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
|
@ -551,7 +556,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@fg:example.com", "pizza", 15),
|
||||
responseEvent("@hi:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
|
@ -573,7 +578,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@wf:example.com", "pizza", 15),
|
||||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
|
@ -594,8 +599,8 @@ describe("MPollBody", () => {
|
|||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@unauthorised:example.com", 5), // Should be ignored
|
||||
endEvent("@me:example.com", 25),
|
||||
newPollEndEvent("@unauthorised:example.com", 5), // Should be ignored
|
||||
newPollEndEvent("@me:example.com", 25),
|
||||
];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
|
@ -620,9 +625,9 @@ describe("MPollBody", () => {
|
|||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [
|
||||
endEvent("@me:example.com", 65),
|
||||
endEvent("@me:example.com", 25),
|
||||
endEvent("@me:example.com", 75),
|
||||
newPollEndEvent("@me:example.com", 65),
|
||||
newPollEndEvent("@me:example.com", 25),
|
||||
newPollEndEvent("@me:example.com", 75),
|
||||
];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
|
@ -640,7 +645,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@qb:example.com", "wings", 14),
|
||||
responseEvent("@xy:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
// Then the winner is highlighted
|
||||
|
@ -658,7 +663,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@xy:example.com", "wings", 15),
|
||||
responseEvent("@fg:example.com", "poutine", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVoteChecked(renderResult, "pizza")).toBe(true);
|
||||
|
@ -669,7 +674,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("highlights nothing if poll has no votes", async () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const renderResult = await newMPollBody([], ends);
|
||||
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(0);
|
||||
});
|
||||
|
@ -681,7 +686,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("says poll is ended if there is an end event", async () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const result = await runIsPollEnded(ends);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
@ -693,9 +698,9 @@ describe("MPollBody", () => {
|
|||
room_id: "#myroom:example.com",
|
||||
content: newPollStart([]),
|
||||
});
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends);
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||
const poll = mockClient.getRoom(pollEvent.getRoomId()!)!.polls.get(pollEvent.getId()!)!;
|
||||
// start fetching, dont await
|
||||
poll.getResponses();
|
||||
|
@ -793,7 +798,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("renders a finished poll with no votes", async () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const { container } = await newMPollBody([], ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
@ -806,7 +811,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@yo:example.com", "wings", 15),
|
||||
responseEvent("@qr:example.com", "italian", 16),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const { container } = await newMPollBody(votes, ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
@ -820,7 +825,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@th:example.com", "poutine", 13),
|
||||
responseEvent("@yh:example.com", "poutine", 14),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const { container } = await newMPollBody(votes, ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
@ -848,7 +853,7 @@ describe("MPollBody", () => {
|
|||
responseEvent("@th:example.com", "poutine", 13),
|
||||
responseEvent("@yh:example.com", "poutine", 14),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
const { container } = await newMPollBody(votes, ends, undefined, false);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
@ -915,28 +920,11 @@ async function newMPollBodyFromEvent(
|
|||
): Promise<RenderResult> {
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
|
||||
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents);
|
||||
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents, mockClient);
|
||||
|
||||
return renderMPollBodyWithWrapper(props);
|
||||
}
|
||||
|
||||
async function setupRoomWithPollEvents(
|
||||
mxEvent: MatrixEvent,
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
): Promise<Room> {
|
||||
const room = new Room(mxEvent.getRoomId()!, mockClient, userId);
|
||||
room.processPollEvents([mxEvent, ...relationEvents, ...endEvents]);
|
||||
setRedactionAllowedForMeOnly(room);
|
||||
// wait for events to process on room
|
||||
await flushPromises();
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
mockClient.relations.mockResolvedValue({
|
||||
events: [...relationEvents, ...endEvents],
|
||||
});
|
||||
return room;
|
||||
}
|
||||
|
||||
function clickOption({ getByTestId }: RenderResult, value: string) {
|
||||
fireEvent.click(getByTestId(`pollOption-${value}`));
|
||||
}
|
||||
|
@ -961,7 +949,7 @@ function endedVotesCount(renderResult: RenderResult, value: string): string {
|
|||
return votesCount(renderResult, value);
|
||||
}
|
||||
|
||||
function newPollStart(answers?: PollAnswer[], question?: string, disclosed = true): PollStartEventContent {
|
||||
export function newPollStart(answers?: PollAnswer[], question?: string, disclosed = true): PollStartEventContent {
|
||||
if (!answers) {
|
||||
answers = [
|
||||
{ id: "pizza", [M_TEXT.name]: "Pizza" },
|
||||
|
@ -1036,22 +1024,8 @@ function expectedResponseEventCall(answer: string) {
|
|||
return [roomId, eventType, content];
|
||||
}
|
||||
|
||||
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: M_POLL_END.name,
|
||||
sender: sender,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$mypoll",
|
||||
},
|
||||
[M_POLL_END.name]: {},
|
||||
[M_TEXT.name]: "The poll has ended. Something.",
|
||||
},
|
||||
});
|
||||
export function newPollEndEvent(sender = "@me:example.com", ts = 0): MatrixEvent {
|
||||
return makePollEndEvent("$mypoll", "#myroom:example.com", sender, ts);
|
||||
}
|
||||
|
||||
async function runIsPollEnded(ends: MatrixEvent[]) {
|
||||
|
@ -1062,7 +1036,7 @@ async function runIsPollEnded(ends: MatrixEvent[]) {
|
|||
content: newPollStart(),
|
||||
});
|
||||
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends);
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||
|
||||
return isPollEnded(pollEvent, mockClient);
|
||||
}
|
||||
|
@ -1078,12 +1052,6 @@ function runFindTopAnswer(votes: MatrixEvent[]) {
|
|||
return findTopAnswer(pollEvent, newVoteRelations(votes));
|
||||
}
|
||||
|
||||
function setRedactionAllowedForMeOnly(room: Room) {
|
||||
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation((_evt: MatrixEvent, id: string) => {
|
||||
return id === userId;
|
||||
});
|
||||
}
|
||||
|
||||
let EVENT_ID = 0;
|
||||
function nextId(): string {
|
||||
EVENT_ID++;
|
||||
|
|
203
test/components/views/messages/MPollEndBody-test.tsx
Normal file
203
test/components/views/messages/MPollEndBody-test.tsx
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { EventTimeline, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
|
||||
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
||||
import { MPollEndBody } from "../../../../src/components/views/messages/MPollEndBody";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
import { 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,
|
||||
onHeightChanged: () => {},
|
||||
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.toJSON());
|
||||
});
|
||||
|
||||
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 } = getComponent();
|
||||
|
||||
// while fetching event, only icon is shown
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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>
|
||||
<div
|
||||
class="mx_MPollEndBody_icon"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MPollEndBody /> when poll start event exists in current timeline renders an ended poll 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MPollBody"
|
||||
>
|
||||
<h2
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
Question?
|
||||
</h2>
|
||||
<div
|
||||
class="mx_MPollBody_allOptions"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_option mx_MPollBody_option_ended"
|
||||
data-testid="pollOption-socks"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_endedOption"
|
||||
data-value="socks"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_optionDescription"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_optionText"
|
||||
>
|
||||
Socks
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_popularityBackground"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_popularityAmount"
|
||||
style="width: 0%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_option mx_MPollBody_option_ended"
|
||||
data-testid="pollOption-shoes"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_endedOption"
|
||||
data-value="shoes"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_optionDescription"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_optionText"
|
||||
>
|
||||
Shoes
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_optionVoteCount"
|
||||
>
|
||||
0 votes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MPollBody_popularityBackground"
|
||||
>
|
||||
<div
|
||||
class="mx_MPollBody_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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -14,16 +14,25 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { M_POLL_START, PollAnswer, M_POLL_KIND_DISCLOSED } from "matrix-js-sdk/src/@types/polls";
|
||||
import { Mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { M_POLL_START, PollAnswer, M_POLL_KIND_DISCLOSED, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
import { uuid4 } from "@sentry/utils";
|
||||
|
||||
import { flushPromises } from "./utilities";
|
||||
|
||||
type Options = {
|
||||
roomId: string;
|
||||
ts: number;
|
||||
id: string;
|
||||
};
|
||||
export const makePollStartEvent = (
|
||||
question: string,
|
||||
sender: string,
|
||||
answers?: PollAnswer[],
|
||||
ts?: number,
|
||||
id?: string,
|
||||
{ roomId, ts, id }: Partial<Options> = {},
|
||||
): MatrixEvent => {
|
||||
if (!answers) {
|
||||
answers = [
|
||||
|
@ -34,7 +43,7 @@ export const makePollStartEvent = (
|
|||
|
||||
return new MatrixEvent({
|
||||
event_id: id || "$mypoll",
|
||||
room_id: "#myroom:example.com",
|
||||
room_id: roomId || "#myroom:example.com",
|
||||
sender: sender,
|
||||
type: M_POLL_START.name,
|
||||
content: {
|
||||
|
@ -50,3 +59,55 @@ export const makePollStartEvent = (
|
|||
origin_server_ts: ts || 0,
|
||||
});
|
||||
};
|
||||
|
||||
export const makePollEndEvent = (pollStartEventId: string, roomId: string, sender: string, ts = 0): MatrixEvent => {
|
||||
return new MatrixEvent({
|
||||
event_id: uuid4(),
|
||||
room_id: roomId,
|
||||
origin_server_ts: ts,
|
||||
type: M_POLL_END.name,
|
||||
sender: sender,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: pollStartEventId,
|
||||
},
|
||||
[M_POLL_END.name]: {},
|
||||
[M_TEXT.name]: "The poll has ended. Something.",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a room with attached poll events
|
||||
* Returns room from mockClient
|
||||
* mocks relations api
|
||||
* @param mxEvent - poll start event
|
||||
* @param relationEvents - returned by relations api
|
||||
* @param endEvents - returned by relations api
|
||||
* @param mockClient - client in use
|
||||
* @returns
|
||||
*/
|
||||
export const setupRoomWithPollEvents = async (
|
||||
mxEvent: MatrixEvent,
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
mockClient: Mocked<MatrixClient>,
|
||||
): Promise<Room> => {
|
||||
const room = new Room(mxEvent.getRoomId()!, mockClient, mockClient.getSafeUserId());
|
||||
room.processPollEvents([mxEvent, ...relationEvents, ...endEvents]);
|
||||
|
||||
// set redaction allowed for current user only
|
||||
// poll end events are validated against this
|
||||
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation((_evt: MatrixEvent, id: string) => {
|
||||
return id === mockClient.getSafeUserId();
|
||||
});
|
||||
|
||||
// wait for events to process on room
|
||||
await flushPromises();
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
mockClient.relations.mockResolvedValue({
|
||||
events: [...relationEvents, ...endEvents],
|
||||
});
|
||||
return room;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue