Add Pin/Unpin action in quick access of the message action bar (#12897)

* Add Pin/Unpin action in quick access of the message action bar

* Add tests for `MessageActionBar`

* Add tests for `PinningUtils`

* Fix `MessageContextMenu-test`

* Add e2e test to pin/unpin from message action bar
This commit is contained in:
Florian Duros 2024-08-21 10:50:00 +02:00 committed by GitHub
parent 4064db1d02
commit 3d80eff65b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 503 additions and 105 deletions

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { fireEvent, render, RenderResult, screen, waitFor } from "@testing-library/react";
import {
EventStatus,
MatrixEvent,
@ -28,9 +28,11 @@ import {
FeatureSupport,
Thread,
M_POLL_KIND_DISCLOSED,
EventTimeline,
} from "matrix-js-sdk/src/matrix";
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { mocked } from "jest-mock";
import userEvent from "@testing-library/user-event";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
@ -83,8 +85,16 @@ describe("MessageContextMenu", () => {
});
describe("message pinning", () => {
let room: Room;
beforeEach(() => {
room = makeDefaultRoom();
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
jest.spyOn(
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"mayClientSendStateEvent",
).mockReturnValue(true);
});
afterAll(() => {
@ -95,25 +105,23 @@ describe("MessageContextMenu", () => {
const eventContent = createMessageEventContent("hello");
const event = new MatrixEvent({ type: EventType.RoomMessage, content: eventContent });
const room = makeDefaultRoom();
// mock permission to disallow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false);
jest.spyOn(
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"mayClientSendStateEvent",
).mockReturnValue(false);
createMenu(event, {}, {}, undefined, room);
createMenu(event, { rightClick: true }, {}, undefined, room);
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy();
});
it("does not show pin option for beacon_info event", () => {
const deadBeaconEvent = makeBeaconInfoEvent("@alice:server.org", roomId, { isLive: false });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
createMenu(deadBeaconEvent, { rightClick: true }, {}, undefined, room);
createMenu(deadBeaconEvent, {}, {}, undefined, room);
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy();
});
it("does not show pin option when pinning feature is disabled", () => {
@ -124,15 +132,12 @@ describe("MessageContextMenu", () => {
room_id: roomId,
});
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// disable pinning feature
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
createMenu(pinnableEvent, {}, {}, undefined, room);
createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room);
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy();
});
it("shows pin option when pinning feature is enabled", () => {
@ -143,16 +148,12 @@ describe("MessageContextMenu", () => {
room_id: roomId,
});
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room);
createMenu(pinnableEvent, {}, {}, undefined, room);
expect(document.querySelector('li[aria-label="Pin"]')).toBeTruthy();
expect(screen.getByRole("menuitem", { name: "Pin" })).toBeTruthy();
});
it("pins event on pin option click", () => {
it("pins event on pin option click", async () => {
const onFinished = jest.fn();
const eventContent = createMessageEventContent("hello");
const pinnableEvent = new MatrixEvent({
@ -162,43 +163,48 @@ describe("MessageContextMenu", () => {
});
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.safeGet();
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "getStateEvents").mockReturnValue({
// @ts-ignore
getContent: () => ({ pinned: ["!1", "!2"] }),
});
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
createMenu(pinnableEvent, { onFinished }, {}, undefined, room);
createMenu(pinnableEvent, { onFinished, rightClick: true }, {}, undefined, room);
fireEvent.click(document.querySelector('li[aria-label="Pin"]')!);
await userEvent.click(screen.getByRole("menuitem", { name: "Pin" }));
// added to account data
expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
event_ids: [
// from account data
"!1",
"!2",
pinnableEvent.getId(),
],
});
await waitFor(() =>
expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
event_ids: [
// from account data
"!1",
"!2",
pinnableEvent.getId(),
],
}),
);
// add to room's pins
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{
pinned: [pinnableEvent.getId()],
},
"",
await waitFor(() =>
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{
pinned: ["!1", "!2", pinnableEvent.getId()],
},
"",
),
);
expect(onFinished).toHaveBeenCalled();
});
it("unpins event on pin option click when event is pinned", () => {
it("unpins event on pin option click when event is pinned", async () => {
const eventContent = createMessageEventContent("hello");
const pinnableEvent = new MatrixEvent({
type: EventType.RoomMessage,
@ -207,7 +213,6 @@ describe("MessageContextMenu", () => {
});
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.safeGet();
const room = makeDefaultRoom();
// make the event already pinned in the room
const pinEvent = new MatrixEvent({
@ -216,18 +221,15 @@ describe("MessageContextMenu", () => {
state_key: "",
content: { pinned: [pinnableEvent.getId(), "!another-event"] },
});
room.currentState.setStateEvents([pinEvent]);
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!.setStateEvents([pinEvent]);
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
createMenu(pinnableEvent, {}, {}, undefined, room);
createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room);
fireEvent.click(document.querySelector('li[aria-label="Unpin"]')!);
await userEvent.click(screen.getByRole("menuitem", { name: "Unpin" }));
expect(client.setRoomAccountData).not.toHaveBeenCalled();