diff --git a/test/components/structures/ThreadPanel-test.tsx b/test/components/structures/ThreadPanel-test.tsx
index 483e8a9b3856..a67d43401765 100644
--- a/test/components/structures/ThreadPanel-test.tsx
+++ b/test/components/structures/ThreadPanel-test.tsx
@@ -14,18 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
-import { render, screen, fireEvent } from "@testing-library/react";
-import { mocked } from "jest-mock";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "focus-visible"; // to fix context menus
+import { mocked } from "jest-mock";
+import React from "react";
+import { MatrixClient, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
+import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from "../../../src/components/structures/ThreadPanel";
+import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
+import RoomContext from "../../../src/contexts/RoomContext";
import { _t } from "../../../src/languageHandler";
-import ResizeNotifier from "../../../src/utils/ResizeNotifier";
-import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
-import { createTestClient, mkStubRoom } from "../../test-utils";
+import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { shouldShowFeedback } from "../../../src/utils/Feedback";
-import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
+import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
+import ResizeNotifier from "../../../src/utils/ResizeNotifier";
+import { createTestClient, getRoomContext, mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
+import { mkThread } from "../../test-utils/threads";
jest.mock("../../../src/utils/Feedback");
@@ -122,4 +127,184 @@ describe("ThreadPanel", () => {
expect(foundButton).toMatchSnapshot();
});
});
+
+ describe("Filtering", () => {
+ const ROOM_ID = "!roomId:example.org";
+ const SENDER = "@alice:example.org";
+
+ let mockClient: MatrixClient;
+ let room: Room;
+
+ const TestThreadPanel = () => (
+
+
+
+
+
+ );
+
+ beforeEach(async () => {
+ jest.clearAllMocks();
+
+ stubClient();
+ mockPlatformPeg();
+ mockClient = mocked(MatrixClientPeg.get());
+ Thread.setServerSideSupport(FeatureSupport.Stable);
+ Thread.setServerSideListSupport(FeatureSupport.Stable);
+ Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable);
+ jest.spyOn(mockClient, "supportsExperimentalThreads").mockReturnValue(true);
+
+ room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
+ pendingEventOrdering: PendingEventOrdering.Detached,
+ });
+ jest.spyOn(room, "fetchRoomThreads").mockReturnValue(Promise.resolve());
+ jest.spyOn(mockClient, "getRoom").mockReturnValue(room);
+ await room.createThreadsTimelineSets();
+ const [allThreads, myThreads] = room.threadsTimelineSets;
+ jest.spyOn(room, "createThreadsTimelineSets")
+ .mockReturnValue(Promise.resolve([allThreads, myThreads]));
+ });
+
+ function toggleThreadFilter(container: HTMLElement, newFilter: ThreadFilterType) {
+ fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown"));
+ const found = screen.queryAllByRole("menuitemradio");
+ expect(found).toHaveLength(2);
+
+ const allThreadsContent = `${_t("All threads")}${_t("Shows all threads from current room")}`;
+ const myThreadsContent = `${_t("My threads")}${_t("Shows all threads you've participated in")}`;
+
+ const allThreadsOption = found.find(it => it.textContent === allThreadsContent);
+ const myThreadsOption = found.find(it => it.textContent === myThreadsContent);
+ expect(allThreadsOption).toBeTruthy();
+ expect(myThreadsOption).toBeTruthy();
+
+ const toSelect = newFilter === ThreadFilterType.My ? myThreadsOption : allThreadsOption;
+ fireEvent.click(toSelect);
+ }
+
+ type EventData = { sender: string, content: string };
+
+ function findEvents(container: HTMLElement): EventData[] {
+ return Array.from(container.querySelectorAll(".mx_EventTile")).map(el => {
+ const sender = el.querySelector(".mx_DisambiguatedProfile_displayName").textContent;
+ const content = el.querySelector(".mx_EventTile_body").textContent;
+ return { sender, content };
+ });
+ }
+
+ function toEventData(event: MatrixEvent): EventData {
+ return { sender: event.event.sender, content: event.event.content.body };
+ }
+
+ it("correctly filters Thread List with multiple threads", async () => {
+ const otherThread = mkThread({
+ room,
+ client: mockClient,
+ authorId: SENDER,
+ participantUserIds: [mockClient.getUserId()],
+ });
+
+ const mixedThread = mkThread({
+ room,
+ client: mockClient,
+ authorId: SENDER,
+ participantUserIds: [SENDER, mockClient.getUserId()],
+ });
+
+ const ownThread = mkThread({
+ room,
+ client: mockClient,
+ authorId: mockClient.getUserId(),
+ participantUserIds: [mockClient.getUserId()],
+ });
+
+ const threadRoots = [otherThread.rootEvent, mixedThread.rootEvent, ownThread.rootEvent];
+ jest.spyOn(mockClient, "fetchRoomEvent").mockImplementation(
+ (_, eventId) => Promise.resolve(threadRoots.find(it => it.getId() === eventId).event),
+ );
+ const [allThreads, myThreads] = room.threadsTimelineSets;
+ allThreads.addLiveEvent(otherThread.rootEvent);
+ allThreads.addLiveEvent(mixedThread.rootEvent);
+ allThreads.addLiveEvent(ownThread.rootEvent);
+ myThreads.addLiveEvent(mixedThread.rootEvent);
+ myThreads.addLiveEvent(ownThread.rootEvent);
+
+ let events: EventData[] = [];
+ const renderResult = render();
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(3);
+ });
+ expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
+ expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
+ expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_ThreadPanel_dropdown")).toBeTruthy());
+ toggleThreadFilter(renderResult.container, ThreadFilterType.My);
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(2);
+ });
+ expect(events[0]).toEqual(toEventData(mixedThread.rootEvent));
+ expect(events[1]).toEqual(toEventData(ownThread.rootEvent));
+ toggleThreadFilter(renderResult.container, ThreadFilterType.All);
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(3);
+ });
+ expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
+ expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
+ expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
+ });
+
+ it("correctly filters Thread List with a single, unparticipated thread", async () => {
+ const otherThread = mkThread({
+ room,
+ client: mockClient,
+ authorId: SENDER,
+ participantUserIds: [mockClient.getUserId()],
+ });
+
+ const threadRoots = [otherThread.rootEvent];
+ jest.spyOn(mockClient, "fetchRoomEvent").mockImplementation(
+ (_, eventId) => Promise.resolve(threadRoots.find(it => it.getId() === eventId).event),
+ );
+ const [allThreads] = room.threadsTimelineSets;
+ allThreads.addLiveEvent(otherThread.rootEvent);
+
+ let events: EventData[] = [];
+ const renderResult = render();
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(1);
+ });
+ expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_ThreadPanel_dropdown")).toBeTruthy());
+ toggleThreadFilter(renderResult.container, ThreadFilterType.My);
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(0);
+ });
+ toggleThreadFilter(renderResult.container, ThreadFilterType.All);
+ await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
+ await waitFor(() => {
+ events = findEvents(renderResult.container);
+ expect(findEvents(renderResult.container)).toHaveLength(1);
+ });
+ expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
+ });
+ })
});