mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 10:45:51 +03:00
Poll history - filter by active or ended (#10098)
* wip * remove dupe * use poll model relations in all cases * update mpollbody tests to use poll instance * update poll fetching login in pinned messages card * add pinned polls to room polls state * add spinner while relations are still loading * handle no poll in end poll dialog * strict errors * render a poll body that errors for poll end events * add fetching logic to pollend tile * extract poll testing utilities * test mpollend * strict fix * more strict fix * strict fix for forwardref * add filter component * update poll test utils * add unstyled filter tab group * filtertabgroup snapshot * lint * update test util setupRoomWithPollEvents to allow testing multiple polls in one room * style filter tabs * test error message for past polls * sort polls list by latest * move FilterTabGroup into generic components * comments * Update src/components/views/dialogs/polls/types.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
f0f50485d7
commit
18ab325eaf
15 changed files with 388 additions and 61 deletions
|
@ -19,6 +19,7 @@
|
|||
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||
@import "./components/views/elements/_FilterTabGroup.pcss";
|
||||
@import "./components/views/elements/_LearnMore.pcss";
|
||||
@import "./components/views/location/_EnableLiveShare.pcss";
|
||||
@import "./components/views/location/_LiveDurationDropdown.pcss";
|
||||
|
|
46
res/css/components/views/elements/_FilterTabGroup.pcss
Normal file
46
res/css/components/views/elements/_FilterTabGroup.pcss
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_FilterTabGroup {
|
||||
color: $primary-content;
|
||||
label {
|
||||
margin-right: $spacing-12;
|
||||
cursor: pointer;
|
||||
span {
|
||||
display: inline-block;
|
||||
line-height: $font-24px;
|
||||
}
|
||||
}
|
||||
input[type="radio"] {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
& + span {
|
||||
color: $secondary-content;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + span {
|
||||
color: $accent;
|
||||
font-weight: $font-semi-bold;
|
||||
// underline
|
||||
box-shadow: 0 1.5px 0 0 currentColor;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,26 +14,47 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent, Poll } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
import { IDialogProps } from "../IDialogProps";
|
||||
import { PollHistoryList } from "./PollHistoryList";
|
||||
import { getPolls } from "./usePollHistory";
|
||||
import { PollHistoryFilter } from "./types";
|
||||
import { usePolls } from "./usePollHistory";
|
||||
|
||||
type PollHistoryDialogProps = Pick<IDialogProps, "onFinished"> & {
|
||||
roomId: string;
|
||||
matrixClient: MatrixClient;
|
||||
};
|
||||
|
||||
const sortEventsByLatest = (left: MatrixEvent, right: MatrixEvent): number => right.getTs() - left.getTs();
|
||||
const filterPolls =
|
||||
(filter: PollHistoryFilter) =>
|
||||
(poll: Poll): boolean =>
|
||||
(filter === "ACTIVE") !== poll.isEnded;
|
||||
const filterAndSortPolls = (polls: Map<string, Poll>, filter: PollHistoryFilter): MatrixEvent[] => {
|
||||
return [...polls.values()]
|
||||
.filter(filterPolls(filter))
|
||||
.map((poll) => poll.rootEvent)
|
||||
.sort(sortEventsByLatest);
|
||||
};
|
||||
|
||||
export const PollHistoryDialog: React.FC<PollHistoryDialogProps> = ({ roomId, matrixClient, onFinished }) => {
|
||||
const pollStartEvents = getPolls(roomId, matrixClient);
|
||||
const { polls } = usePolls(roomId, matrixClient);
|
||||
const [filter, setFilter] = useState<PollHistoryFilter>("ACTIVE");
|
||||
const [pollStartEvents, setPollStartEvents] = useState(filterAndSortPolls(polls, filter));
|
||||
|
||||
useEffect(() => {
|
||||
setPollStartEvents(filterAndSortPolls(polls, filter));
|
||||
}, [filter, polls]);
|
||||
|
||||
return (
|
||||
<BaseDialog title={_t("Polls history")} onFinished={onFinished}>
|
||||
<div className="mx_PollHistoryDialog_content">
|
||||
<PollHistoryList pollStartEvents={pollStartEvents} />
|
||||
<PollHistoryList pollStartEvents={pollStartEvents} filter={filter} onFilterChange={setFilter} />
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -19,13 +19,26 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import PollListItem from "./PollListItem";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { FilterTabGroup } from "../../elements/FilterTabGroup";
|
||||
import { PollHistoryFilter } from "./types";
|
||||
|
||||
type PollHistoryListProps = {
|
||||
pollStartEvents: MatrixEvent[];
|
||||
filter: PollHistoryFilter;
|
||||
onFilterChange: (filter: PollHistoryFilter) => void;
|
||||
};
|
||||
export const PollHistoryList: React.FC<PollHistoryListProps> = ({ pollStartEvents }) => {
|
||||
export const PollHistoryList: React.FC<PollHistoryListProps> = ({ pollStartEvents, filter, onFilterChange }) => {
|
||||
return (
|
||||
<div className="mx_PollHistoryList">
|
||||
<FilterTabGroup<PollHistoryFilter>
|
||||
name="PollHistoryDialog_filter"
|
||||
value={filter}
|
||||
onFilterChange={onFilterChange}
|
||||
tabs={[
|
||||
{ id: "ACTIVE", label: "Active polls" },
|
||||
{ id: "ENDED", label: "Past polls" },
|
||||
]}
|
||||
/>
|
||||
{!!pollStartEvents.length ? (
|
||||
<ol className="mx_PollHistoryList_list">
|
||||
{pollStartEvents.map((pollStartEvent) => (
|
||||
|
@ -33,7 +46,11 @@ export const PollHistoryList: React.FC<PollHistoryListProps> = ({ pollStartEvent
|
|||
))}
|
||||
</ol>
|
||||
) : (
|
||||
<span className="mx_PollHistoryList_noResults">{_t("There are no polls in this room")}</span>
|
||||
<span className="mx_PollHistoryList_noResults">
|
||||
{filter === "ACTIVE"
|
||||
? _t("There are no active polls in this room")
|
||||
: _t("There are no past polls in this room")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
22
src/components/views/dialogs/polls/types.ts
Normal file
22
src/components/views/dialogs/polls/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Possible values for the "filter" setting in the poll history dialog
|
||||
*
|
||||
* Ended polls have a valid M_POLL_END event
|
||||
*/
|
||||
export type PollHistoryFilter = "ACTIVE" | "ENDED";
|
|
@ -14,27 +14,32 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { Poll, PollEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
|
||||
|
||||
/**
|
||||
* Get poll start events in a rooms live timeline
|
||||
* Get poll instances from a room
|
||||
* @param roomId - id of room to retrieve polls for
|
||||
* @param matrixClient - client
|
||||
* @returns {MatrixEvent[]} - array fo poll start events
|
||||
* @returns {Map<string, Poll>} - Map of Poll instances
|
||||
*/
|
||||
export const getPolls = (roomId: string, matrixClient: MatrixClient): MatrixEvent[] => {
|
||||
export const usePolls = (
|
||||
roomId: string,
|
||||
matrixClient: MatrixClient,
|
||||
): {
|
||||
polls: Map<string, Poll>;
|
||||
} => {
|
||||
const room = matrixClient.getRoom(roomId);
|
||||
|
||||
if (!room) {
|
||||
throw new Error("Cannot find room");
|
||||
}
|
||||
|
||||
// @TODO(kerrya) poll history will be actively fetched in PSG-1043
|
||||
// for now, just display polls that are in the current timeline
|
||||
const timelineEvents = room.getLiveTimeline().getEvents();
|
||||
const pollStartEvents = timelineEvents.filter((event) => M_POLL_START.matches(event.getType()));
|
||||
const polls = useEventEmitterState(room, PollEvent.New, () => room.polls);
|
||||
|
||||
return pollStartEvents;
|
||||
// @TODO(kerrya) watch polls for end events, trigger refiltering
|
||||
|
||||
return { polls };
|
||||
};
|
||||
|
|
57
src/components/views/elements/FilterTabGroup.tsx
Normal file
57
src/components/views/elements/FilterTabGroup.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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, { FieldsetHTMLAttributes, ReactNode } from "react";
|
||||
|
||||
export type FilterTab<T> = {
|
||||
label: string | ReactNode;
|
||||
id: T;
|
||||
};
|
||||
type FilterTabGroupProps<T extends string = string> = FieldsetHTMLAttributes<any> & {
|
||||
// group name used for radio buttons
|
||||
name: string;
|
||||
onFilterChange: (id: T) => void;
|
||||
// active tab's id
|
||||
value: T;
|
||||
// tabs to display
|
||||
tabs: FilterTab<T>[];
|
||||
};
|
||||
|
||||
/**
|
||||
* React component which styles a set of content filters as tabs
|
||||
*
|
||||
* This is used in displays which show a list of content items, and the user can select between one of several
|
||||
* filters for those items. For example, in the Poll History dialog, the user can select between "Active" and "Ended"
|
||||
* polls.
|
||||
*
|
||||
* Type `T` is used for the `value` attribute for the buttons in the radio group.
|
||||
*/
|
||||
export const FilterTabGroup = <T extends string = string>({
|
||||
name,
|
||||
value,
|
||||
tabs,
|
||||
onFilterChange,
|
||||
...rest
|
||||
}: FilterTabGroupProps<T>): JSX.Element => (
|
||||
<fieldset {...rest} className="mx_FilterTabGroup">
|
||||
{tabs.map(({ label, id }) => (
|
||||
<label data-testid={`filter-tab-${name}-${id}`} key={id}>
|
||||
<input type="radio" name={name} value={id} onChange={() => onFilterChange(id)} checked={value === id} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
);
|
|
@ -3144,7 +3144,8 @@
|
|||
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.",
|
||||
"Access your secure message history and set up secure messaging by entering your Security Key.": "Access your secure message history and set up secure messaging by entering your Security Key.",
|
||||
"If you've forgotten your Security Key you can <button>set up new recovery options</button>": "If you've forgotten your Security Key you can <button>set up new recovery options</button>",
|
||||
"There are no polls in this room": "There are no polls in this room",
|
||||
"There are no active polls in this room": "There are no active polls in this room",
|
||||
"There are no past polls in this room": "There are no past polls in this room",
|
||||
"Send custom account data event": "Send custom account data event",
|
||||
"Send custom room account data event": "Send custom room account data event",
|
||||
"Event Type": "Event Type",
|
||||
|
|
|
@ -15,15 +15,17 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/polls/PollHistoryDialog";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
makePollEndEvent,
|
||||
makePollStartEvent,
|
||||
mockClientMethodsUser,
|
||||
mockIntlDateTimeFormat,
|
||||
setupRoomWithPollEvents,
|
||||
unmockIntlDateTimeFormat,
|
||||
} from "../../../../test-utils";
|
||||
|
||||
|
@ -33,6 +35,8 @@ describe("<PollHistoryDialog />", () => {
|
|||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getRoom: jest.fn(),
|
||||
relations: jest.fn(),
|
||||
decryptEventIfNeeded: jest.fn(),
|
||||
});
|
||||
const room = new Room(roomId, mockClient, userId);
|
||||
|
||||
|
@ -49,6 +53,7 @@ describe("<PollHistoryDialog />", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
mockClient.relations.mockResolvedValue({ events: [] });
|
||||
const timeline = room.getLiveTimeline();
|
||||
jest.spyOn(timeline, "getEvents").mockReturnValue([]);
|
||||
});
|
||||
|
@ -63,24 +68,58 @@ describe("<PollHistoryDialog />", () => {
|
|||
expect(() => getComponent()).toThrow("Cannot find room");
|
||||
});
|
||||
|
||||
it("renders a no polls message when there are no polls in the timeline", () => {
|
||||
it("renders a no polls message when there are no active polls in the timeline", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(getByText("There are no polls in this room")).toBeTruthy();
|
||||
expect(getByText("There are no active polls in this room")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders a list of polls when there are polls in the timeline", async () => {
|
||||
it("renders a no past polls message when there are no past polls in the timeline", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText("Past polls"));
|
||||
|
||||
expect(getByText("There are no past polls in this room")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders a list of active polls when there are polls in the timeline", async () => {
|
||||
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);
|
||||
await setupRoomWithPollEvents([pollStart2, pollStart3, pollStart1], [], [pollEnd3], mockClient, room);
|
||||
|
||||
const { container, queryByText, getByTestId } = getComponent();
|
||||
|
||||
expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
// this poll is ended, and default filter is ACTIVE
|
||||
expect(queryByText("What?")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("filters ended polls", 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: {},
|
||||
});
|
||||
const timeline = room.getLiveTimeline();
|
||||
jest.spyOn(timeline, "getEvents").mockReturnValue([pollStart1, pollStart2, pollStart3, message]);
|
||||
const { container } = getComponent();
|
||||
const pollEnd3 = makePollEndEvent(pollStart3.getId()!, roomId, userId, 1675200725090 + 1);
|
||||
await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
const { getByText, queryByText, getByTestId } = getComponent();
|
||||
|
||||
expect(getByText("Question?")).toBeInTheDocument();
|
||||
expect(getByText("Where?")).toBeInTheDocument();
|
||||
// this poll is ended, and default filter is ACTIVE
|
||||
expect(queryByText("What?")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getByText("Past polls"));
|
||||
expect(getByTestId("filter-tab-PollHistoryDialog_filter-ENDED").firstElementChild).toBeChecked();
|
||||
|
||||
// active polls no longer shown
|
||||
expect(queryByText("Question?")).not.toBeInTheDocument();
|
||||
expect(queryByText("Where?")).not.toBeInTheDocument();
|
||||
// this poll is ended
|
||||
expect(getByText("What?")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<PollHistoryDialog /> renders a list of polls when there are polls in the timeline 1`] = `
|
||||
exports[`<PollHistoryDialog /> renders a list of active polls when there are polls in the timeline 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
|
@ -35,25 +35,38 @@ exports[`<PollHistoryDialog /> renders a list of polls when there are polls in t
|
|||
<div
|
||||
class="mx_PollHistoryList"
|
||||
>
|
||||
<fieldset
|
||||
class="mx_FilterTabGroup"
|
||||
>
|
||||
<label
|
||||
data-testid="filter-tab-PollHistoryDialog_filter-ACTIVE"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
name="PollHistoryDialog_filter"
|
||||
type="radio"
|
||||
value="ACTIVE"
|
||||
/>
|
||||
<span>
|
||||
Active polls
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
data-testid="filter-tab-PollHistoryDialog_filter-ENDED"
|
||||
>
|
||||
<input
|
||||
name="PollHistoryDialog_filter"
|
||||
type="radio"
|
||||
value="ENDED"
|
||||
/>
|
||||
<span>
|
||||
Past polls
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<ol
|
||||
class="mx_PollHistoryList_list"
|
||||
>
|
||||
<li
|
||||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$1"
|
||||
>
|
||||
<span>
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$2"
|
||||
|
@ -72,10 +85,10 @@ exports[`<PollHistoryDialog /> renders a list of polls when there are polls in t
|
|||
</li>
|
||||
<li
|
||||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$3"
|
||||
data-testid="pollListItem-$1"
|
||||
>
|
||||
<span>
|
||||
31/01/23
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
|
@ -83,7 +96,7 @@ exports[`<PollHistoryDialog /> renders a list of polls when there are polls in t
|
|||
<span
|
||||
class="mx_PollListItem_question"
|
||||
>
|
||||
What?
|
||||
Question?
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
|
54
test/components/views/elements/FilterTabGroup-test.tsx
Normal file
54
test/components/views/elements/FilterTabGroup-test.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
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 { fireEvent, render } from "@testing-library/react";
|
||||
|
||||
import { FilterTabGroup } from "../../../../src/components/views/elements/FilterTabGroup";
|
||||
|
||||
describe("<FilterTabGroup />", () => {
|
||||
enum TestOption {
|
||||
Apple = "Apple",
|
||||
Banana = "Banana",
|
||||
Orange = "Orange",
|
||||
}
|
||||
const defaultProps = {
|
||||
"name": "test",
|
||||
"value": TestOption.Apple,
|
||||
"onFilterChange": jest.fn(),
|
||||
"tabs": [
|
||||
{ id: TestOption.Apple, label: `Label for ${TestOption.Apple}` },
|
||||
{ id: TestOption.Banana, label: `Label for ${TestOption.Banana}` },
|
||||
{ id: TestOption.Orange, label: `Label for ${TestOption.Orange}` },
|
||||
],
|
||||
"data-testid": "test",
|
||||
};
|
||||
const getComponent = (props = {}) => <FilterTabGroup<TestOption> {...defaultProps} {...props} />;
|
||||
|
||||
it("renders options", () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls onChange handler on selection", () => {
|
||||
const onFilterChange = jest.fn();
|
||||
const { getByText } = render(getComponent({ onFilterChange }));
|
||||
|
||||
fireEvent.click(getByText("Label for Banana"));
|
||||
|
||||
expect(onFilterChange).toHaveBeenCalledWith(TestOption.Banana);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<FilterTabGroup /> renders options 1`] = `
|
||||
<div>
|
||||
<fieldset
|
||||
class="mx_FilterTabGroup"
|
||||
data-testid="test"
|
||||
>
|
||||
<label
|
||||
data-testid="filter-tab-test-Apple"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
name="test"
|
||||
type="radio"
|
||||
value="Apple"
|
||||
/>
|
||||
<span>
|
||||
Label for Apple
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
data-testid="filter-tab-test-Banana"
|
||||
>
|
||||
<input
|
||||
name="test"
|
||||
type="radio"
|
||||
value="Banana"
|
||||
/>
|
||||
<span>
|
||||
Label for Banana
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
data-testid="filter-tab-test-Orange"
|
||||
>
|
||||
<input
|
||||
name="test"
|
||||
type="radio"
|
||||
value="Orange"
|
||||
/>
|
||||
<span>
|
||||
Label for Orange
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
`;
|
|
@ -227,7 +227,7 @@ describe("MPollBody", () => {
|
|||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||
const room = await setupRoomWithPollEvents([mxEvent], votes, [], mockClient);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
// wait for /relations promise to resolve
|
||||
await flushPromises();
|
||||
|
@ -255,7 +255,7 @@ describe("MPollBody", () => {
|
|||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||
const room = await setupRoomWithPollEvents([mxEvent], votes, [], mockClient);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
// wait for /relations promise to resolve
|
||||
await flushPromises();
|
||||
|
@ -700,7 +700,7 @@ describe("MPollBody", () => {
|
|||
});
|
||||
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||
await setupRoomWithPollEvents([pollEvent], [], ends, mockClient);
|
||||
const poll = mockClient.getRoom(pollEvent.getRoomId()!)!.polls.get(pollEvent.getId()!)!;
|
||||
// start fetching, dont await
|
||||
poll.getResponses();
|
||||
|
@ -920,7 +920,7 @@ async function newMPollBodyFromEvent(
|
|||
): Promise<RenderResult> {
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||
|
||||
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents, mockClient);
|
||||
await setupRoomWithPollEvents([mxEvent], relationEvents, endEvents, mockClient);
|
||||
|
||||
return renderMPollBodyWithWrapper(props);
|
||||
}
|
||||
|
@ -1036,7 +1036,7 @@ async function runIsPollEnded(ends: MatrixEvent[]) {
|
|||
content: newPollStart(),
|
||||
});
|
||||
|
||||
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||
await setupRoomWithPollEvents([pollEvent], [], ends, mockClient);
|
||||
|
||||
return isPollEnded(pollEvent, mockClient);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ describe("<MPollEndBody />", () => {
|
|||
|
||||
const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise<Room> => {
|
||||
if (pollStart) {
|
||||
await setupRoomWithPollEvents(pollStart, [], [pollEnd], mockClient);
|
||||
await setupRoomWithPollEvents([pollStart], [], [pollEnd], mockClient);
|
||||
}
|
||||
const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId);
|
||||
|
||||
|
|
|
@ -89,13 +89,14 @@ export const makePollEndEvent = (pollStartEventId: string, roomId: string, sende
|
|||
* @returns
|
||||
*/
|
||||
export const setupRoomWithPollEvents = async (
|
||||
mxEvent: MatrixEvent,
|
||||
pollStartEvents: MatrixEvent[],
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
mockClient: Mocked<MatrixClient>,
|
||||
existingRoom?: Room,
|
||||
): Promise<Room> => {
|
||||
const room = new Room(mxEvent.getRoomId()!, mockClient, mockClient.getSafeUserId());
|
||||
room.processPollEvents([mxEvent, ...relationEvents, ...endEvents]);
|
||||
const room = existingRoom || new Room(pollStartEvents[0].getRoomId()!, mockClient, mockClient.getSafeUserId());
|
||||
room.processPollEvents([...pollStartEvents, ...relationEvents, ...endEvents]);
|
||||
|
||||
// set redaction allowed for current user only
|
||||
// poll end events are validated against this
|
||||
|
@ -106,8 +107,10 @@ export const setupRoomWithPollEvents = async (
|
|||
// wait for events to process on room
|
||||
await flushPromises();
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
mockClient.relations.mockResolvedValue({
|
||||
events: [...relationEvents, ...endEvents],
|
||||
mockClient.relations.mockImplementation(async (_roomId: string, eventId: string) => {
|
||||
return {
|
||||
events: [...relationEvents, ...endEvents].filter((event) => event.getRelation()?.event_id === eventId),
|
||||
};
|
||||
});
|
||||
return room;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue