Pop out of Threads Activity Centre (#12136)
* Add `Thread Activity centre` labs flag * Rename translation string * WIP Thread Activity Centre * Update supportedLevels * css lint * i18n lint * Fix labs subsection test * Update Threads Activity Centre label * Rename Thread Activity Centre to Threads Activity Centre * Use compound `MenuItem` instead of custom button * Color thread icon when hovered * Make the pop-up scrollable and add a max height * Remove Math.random in key * Remove unused class * Change add comments on `mx_ThreadsActivityRows` and `mx_ThreadsActivityRow` * Make threads activity centre labs flag split out unread counts Just shows notif & unread counts for main thread if the TAC is enabled. * Fix tests * Simpler fix * Open thread panel when thread clicke in Threads Activity Centre Hopefully this is a sensible enough way. The panel will stay open of course (ie. if you go to a different room & come back), but that's the nature of the right panel. * Dynamic state of room * Add doc * Use the StatelessNotificationBadge component in ThreadsActivityCentre and re-use the existing NotificationLevel * Remove unused style * Add room sorting * Fix `ThreadsActivityRow` props doc * Pass in & cache the status of the TAC labs flag * Pass includeThreads as setting to doesRoomHaveUnreadMessages too * Fix tests * Add analytics to the TAC (#12179) * Update TAC label (#12186) * Add `IndicatorIcon` to the TAC button (#12182) Add `IndicatorIcon` to the TAC button * Threads don't have activity if the room is muted This makes it match the computation in determineUnreadState. Ideally this logic should all be in one place. * Re-use doesRoomHaveUnreadThreads for useRoomThreadNotifications This incorporates the logic of not showing unread dots if the room is muted * Add TAC description in labs (#12197) * Fox position & size of dot on the tac button IndicatorIcon doesn't like having the size of its icon adjusted and we probably shouldn't do it anyway: better to specify to the component what size we want it. * TAC: Utils tests (#12200) * Add tests for `doesRoomHaveUnreadThreads` * Add tests for `getThreadNotificationLevel` * Add test for the ThreadsActivityCentre component * Add snapshot test * Fix narrow hover background on TAC button Make the button 32x32 (and the inner icon 24x24) * Add caption for empty TAC * s/tac/threads_activity_centre/ * Fix i18n & add tests * Add playwright tests for the TAC (#12227) * Fox comments --------- Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
parent
3052025dd0
commit
a4987060b7
24 changed files with 1455 additions and 14 deletions
176
test/components/views/spaces/ThreadsActivityCentre-test.tsx
Normal file
176
test/components/views/spaces/ThreadsActivityCentre-test.tsx
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2024 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 { getByText, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { NotificationCountType, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { ThreadsActivityCentre } from "../../../../src/components/views/spaces/threads-activity-centre";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { populateThread } from "../../../test-utils/threads";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
|
||||
describe("ThreadsActivityCentre", () => {
|
||||
const getTACButton = () => {
|
||||
return screen.getByRole("button", { name: "Threads" });
|
||||
};
|
||||
|
||||
const getTACMenu = () => {
|
||||
return screen.getByRole("menu");
|
||||
};
|
||||
|
||||
const renderTAC = () => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<ThreadsActivityCentre />
|
||||
);
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
const cli = stubClient();
|
||||
cli.supportsThreads = () => true;
|
||||
|
||||
const roomWithActivity = new Room("!room:server", cli, cli.getSafeUserId(), {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
roomWithActivity.name = "Just activity";
|
||||
|
||||
const roomWithNotif = new Room("!room:server", cli, cli.getSafeUserId(), {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
roomWithNotif.name = "A notification";
|
||||
|
||||
const roomWithHighlight = new Room("!room:server", cli, cli.getSafeUserId(), {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
roomWithHighlight.name = "This is a real highlight";
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(cli);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(cli);
|
||||
|
||||
const dmRoomMap = new DMRoomMap(cli);
|
||||
jest.spyOn(dmRoomMap, "getUserIdForRoomId");
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||
|
||||
await populateThread({
|
||||
room: roomWithActivity,
|
||||
client: cli,
|
||||
authorId: "@foo:bar",
|
||||
participantUserIds: ["@fee:bar"],
|
||||
});
|
||||
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: roomWithNotif,
|
||||
client: cli,
|
||||
authorId: "@foo:bar",
|
||||
participantUserIds: ["@fee:bar"],
|
||||
});
|
||||
roomWithNotif.setThreadUnreadNotificationCount(notifThreadInfo.thread.id, NotificationCountType.Total, 1);
|
||||
|
||||
const highlightThreadInfo = await populateThread({
|
||||
room: roomWithHighlight,
|
||||
client: cli,
|
||||
authorId: "@foo:bar",
|
||||
participantUserIds: ["@fee:bar"],
|
||||
});
|
||||
roomWithHighlight.setThreadUnreadNotificationCount(
|
||||
highlightThreadInfo.thread.id,
|
||||
NotificationCountType.Highlight,
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it("should render the threads activity centre button", async () => {
|
||||
renderTAC();
|
||||
expect(getTACButton()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the threads activity centre menu when the button is clicked", async () => {
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
expect(getTACMenu()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a room with a activity in the TAC", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithActivity]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
const tacRows = screen.getAllByRole("menuitem");
|
||||
expect(tacRows.length).toEqual(1);
|
||||
|
||||
getByText(tacRows[0], "Just activity");
|
||||
expect(tacRows[0].getElementsByClassName("mx_NotificationBadge").length).toEqual(1);
|
||||
expect(tacRows[0].getElementsByClassName("mx_NotificationBadge_level_notification").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("should render a room with a regular notification in the TAC", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithNotif]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
const tacRows = screen.getAllByRole("menuitem");
|
||||
expect(tacRows.length).toEqual(1);
|
||||
|
||||
getByText(tacRows[0], "A notification");
|
||||
expect(tacRows[0].getElementsByClassName("mx_NotificationBadge_level_notification").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should render a room with a highlight notification in the TAC", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithHighlight]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
const tacRows = screen.getAllByRole("menuitem");
|
||||
expect(tacRows.length).toEqual(1);
|
||||
|
||||
getByText(tacRows[0], "This is a real highlight");
|
||||
expect(tacRows[0].getElementsByClassName("mx_NotificationBadge_level_highlight").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("renders notifications matching the snapshot", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithHighlight, roomWithNotif, roomWithActivity]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
expect(screen.getByRole("menu")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display a caption when no threads are unread", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
expect(screen.getByRole("menu").getElementsByClassName("mx_ThreadsActivityCentre_emptyCaption").length).toEqual(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it("should match snapshot when empty", async () => {
|
||||
cli.getVisibleRooms = jest.fn().mockReturnValue([]);
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
||||
expect(screen.getByRole("menu")).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,211 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-13"
|
||||
aria-orientation="vertical"
|
||||
class="_menu_1x5h1_17"
|
||||
data-align="end"
|
||||
data-orientation="vertical"
|
||||
data-radix-menu-content=""
|
||||
data-side="right"
|
||||
data-state="open"
|
||||
dir="ltr"
|
||||
id="radix-14"
|
||||
role="menu"
|
||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
|
||||
id=":r5:"
|
||||
>
|
||||
Threads activity
|
||||
</h3>
|
||||
<div
|
||||
class="mx_ThreadsActivity_rows"
|
||||
>
|
||||
<button
|
||||
class="mx_ThreadsActivityRow _item_1bcsk_17 _interactive_1bcsk_36"
|
||||
data-kind="primary"
|
||||
data-orientation="vertical"
|
||||
data-radix-collection-item=""
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="_icon_1bcsk_44"
|
||||
>
|
||||
<span
|
||||
class="_avatar_1o69u_17 mx_BaseAvatar _avatar-imageless_1o69u_60"
|
||||
data-color="8"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
>
|
||||
T
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_1bcsk_53"
|
||||
>
|
||||
This is a real highlight
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_1bcsk_60"
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.70005 17.3C8.51672 17.1167 8.42505 16.8834 8.42505 16.6C8.42505 16.3167 8.51672 16.0834 8.70005 15.9L12.6 12L8.70005 8.10005C8.51672 7.91672 8.42505 7.68338 8.42505 7.40005C8.42505 7.11672 8.51672 6.88338 8.70005 6.70005C8.88338 6.51672 9.11672 6.42505 9.40005 6.42505C9.68338 6.42505 9.91672 6.51672 10.1 6.70005L14.7 11.3C14.8 11.4 14.8709 11.5084 14.9125 11.625C14.9542 11.7417 14.975 11.8667 14.975 12C14.975 12.1334 14.9542 12.2584 14.9125 12.375C14.8709 12.4917 14.8 12.6 14.7 12.7L10.1 17.3C9.91672 17.4834 9.68338 17.575 9.40005 17.575C9.11672 17.575 8.88338 17.4834 8.70005 17.3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_highlight mx_NotificationBadge_dot"
|
||||
>
|
||||
<span
|
||||
class="mx_NotificationBadge_count"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="mx_ThreadsActivityRow _item_1bcsk_17 _interactive_1bcsk_36"
|
||||
data-kind="primary"
|
||||
data-orientation="vertical"
|
||||
data-radix-collection-item=""
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="_icon_1bcsk_44"
|
||||
>
|
||||
<span
|
||||
class="_avatar_1o69u_17 mx_BaseAvatar _avatar-imageless_1o69u_60"
|
||||
data-color="8"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
>
|
||||
A
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_1bcsk_53"
|
||||
>
|
||||
A notification
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_1bcsk_60"
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.70005 17.3C8.51672 17.1167 8.42505 16.8834 8.42505 16.6C8.42505 16.3167 8.51672 16.0834 8.70005 15.9L12.6 12L8.70005 8.10005C8.51672 7.91672 8.42505 7.68338 8.42505 7.40005C8.42505 7.11672 8.51672 6.88338 8.70005 6.70005C8.88338 6.51672 9.11672 6.42505 9.40005 6.42505C9.68338 6.42505 9.91672 6.51672 10.1 6.70005L14.7 11.3C14.8 11.4 14.8709 11.5084 14.9125 11.625C14.9542 11.7417 14.975 11.8667 14.975 12C14.975 12.1334 14.9542 12.2584 14.9125 12.375C14.8709 12.4917 14.8 12.6 14.7 12.7L10.1 17.3C9.91672 17.4834 9.68338 17.575 9.40005 17.575C9.11672 17.575 8.88338 17.4834 8.70005 17.3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_notification mx_NotificationBadge_dot"
|
||||
>
|
||||
<span
|
||||
class="mx_NotificationBadge_count"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="mx_ThreadsActivityRow _item_1bcsk_17 _interactive_1bcsk_36"
|
||||
data-kind="primary"
|
||||
data-orientation="vertical"
|
||||
data-radix-collection-item=""
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="_icon_1bcsk_44"
|
||||
>
|
||||
<span
|
||||
class="_avatar_1o69u_17 mx_BaseAvatar _avatar-imageless_1o69u_60"
|
||||
data-color="8"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
>
|
||||
J
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_1bcsk_53"
|
||||
>
|
||||
Just activity
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_1bcsk_60"
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.70005 17.3C8.51672 17.1167 8.42505 16.8834 8.42505 16.6C8.42505 16.3167 8.51672 16.0834 8.70005 15.9L12.6 12L8.70005 8.10005C8.51672 7.91672 8.42505 7.68338 8.42505 7.40005C8.42505 7.11672 8.51672 6.88338 8.70005 6.70005C8.88338 6.51672 9.11672 6.42505 9.40005 6.42505C9.68338 6.42505 9.91672 6.51672 10.1 6.70005L14.7 11.3C14.8 11.4 14.8709 11.5084 14.9125 11.625C14.9542 11.7417 14.975 11.8667 14.975 12C14.975 12.1334 14.9542 12.2584 14.9125 12.375C14.8709 12.4917 14.8 12.6 14.7 12.7L10.1 17.3C9.91672 17.4834 9.68338 17.575 9.40005 17.575C9.11672 17.575 8.88338 17.4834 8.70005 17.3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_dot"
|
||||
>
|
||||
<span
|
||||
class="mx_NotificationBadge_count"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-20"
|
||||
aria-orientation="vertical"
|
||||
class="_menu_1x5h1_17"
|
||||
data-align="end"
|
||||
data-orientation="vertical"
|
||||
data-radix-menu-content=""
|
||||
data-side="right"
|
||||
data-state="open"
|
||||
dir="ltr"
|
||||
id="radix-21"
|
||||
role="menu"
|
||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
|
||||
id=":r7:"
|
||||
>
|
||||
Threads activity
|
||||
</h3>
|
||||
<div
|
||||
class="mx_ThreadsActivity_rows"
|
||||
>
|
||||
<div
|
||||
class="mx_ThreadsActivityCentre_emptyCaption"
|
||||
>
|
||||
You don't have rooms with unread threads yet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Add table
Add a link
Reference in a new issue