Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/cr/72
# Conflicts: # src/components/views/rooms/RoomHeader.tsx # test/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap
This commit is contained in:
commit
c839123b83
123 changed files with 6527 additions and 6069 deletions
|
@ -112,6 +112,7 @@ describe("ThreadView", () => {
|
|||
"rel_type": RelationType.Thread,
|
||||
},
|
||||
"msgtype": MsgType.Text,
|
||||
"m.mentions": {},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -446,7 +446,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
data-testid="basicmessagecomposer"
|
||||
dir="auto"
|
||||
role="textbox"
|
||||
style="--placeholder: 'Send a message…';"
|
||||
style="--placeholder: 'Send\\ a\\ message…';"
|
||||
tabindex="0"
|
||||
translate="no"
|
||||
>
|
||||
|
@ -687,7 +687,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
data-testid="basicmessagecomposer"
|
||||
dir="auto"
|
||||
role="textbox"
|
||||
style="--placeholder: 'Send a message…';"
|
||||
style="--placeholder: 'Send\\ a\\ message…';"
|
||||
tabindex="0"
|
||||
translate="no"
|
||||
>
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { EventEmitter } from "events";
|
||||
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import UserInfo, {
|
||||
BanToggleButton,
|
||||
|
@ -134,6 +135,8 @@ beforeEach(() => {
|
|||
mockCrypto = mocked({
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
getUserVerificationStatus: jest.fn(),
|
||||
} as unknown as CryptoApi);
|
||||
|
||||
mockClient = mocked({
|
||||
|
@ -161,7 +164,6 @@ beforeEach(() => {
|
|||
setPowerLevel: jest.fn(),
|
||||
downloadKeys: jest.fn(),
|
||||
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||
getStoredCrossSigningForUser: jest.fn(),
|
||||
} as unknown as MatrixClient);
|
||||
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
|
@ -378,6 +380,7 @@ describe("<UserInfo />", () => {
|
|||
|
||||
it("renders unverified user info", async () => {
|
||||
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
|
@ -389,6 +392,7 @@ describe("<UserInfo />", () => {
|
|||
|
||||
it("renders verified user info", async () => {
|
||||
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(true, false, false));
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
|
|
|
@ -17,13 +17,6 @@ exports[`LegacyRoomHeaderButtons-test.tsx should render 1`] = `
|
|||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-current="false"
|
||||
aria-label="Notifications"
|
||||
class="mx_AccessibleButton mx_LegacyRoomHeader_button mx_RightPanel_notifsButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-current="false"
|
||||
aria-label="Room info"
|
||||
|
|
|
@ -94,6 +94,22 @@ describe("BasicMessageComposer", () => {
|
|||
const transformedText = model.parts.map((part) => part.text).join("");
|
||||
expect(transformedText).toBe("/plain foobar\n");
|
||||
});
|
||||
|
||||
it("should escape single quote in placeholder", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
const composer = render(<BasicMessageComposer placeholder={"Don't"} model={model} room={room} />);
|
||||
const input = composer.queryAllByRole("textbox");
|
||||
const placeholder = input[0].style.getPropertyValue("--placeholder");
|
||||
expect(placeholder).toMatch("'Don\\'t'");
|
||||
});
|
||||
|
||||
it("should escape backslash in placeholder", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
const composer = render(<BasicMessageComposer placeholder={"w\\e"} model={model} room={room} />);
|
||||
const input = composer.queryAllByRole("textbox");
|
||||
const placeholder = input[0].style.getPropertyValue("--placeholder");
|
||||
expect(placeholder).toMatch("'w\\\\e'");
|
||||
});
|
||||
});
|
||||
|
||||
function generateMockDataTransferForString(string: string): DataTransfer {
|
||||
|
|
|
@ -137,13 +137,15 @@ describe("<EditMessageComposer/>", () => {
|
|||
...editedEvent.getContent(),
|
||||
"body": " * original message + edit",
|
||||
"m.new_content": {
|
||||
body: "original message + edit",
|
||||
msgtype: "m.text",
|
||||
"body": "original message + edit",
|
||||
"msgtype": "m.text",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
};
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(editedEvent.getRoomId()!, null, expectedBody);
|
||||
});
|
||||
|
@ -168,13 +170,15 @@ describe("<EditMessageComposer/>", () => {
|
|||
"body": " * hello world",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
body: "hello world",
|
||||
msgtype: "m.text",
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -191,15 +195,17 @@ describe("<EditMessageComposer/>", () => {
|
|||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * hello <em>world</em>",
|
||||
"m.new_content": {
|
||||
body: "hello *world*",
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "hello <em>world</em>",
|
||||
"body": "hello *world*",
|
||||
"msgtype": "m.text",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "hello <em>world</em>",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -216,15 +222,17 @@ describe("<EditMessageComposer/>", () => {
|
|||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * blinks <strong>quickly</strong>",
|
||||
"m.new_content": {
|
||||
body: "blinks __quickly__",
|
||||
msgtype: "m.emote",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "blinks <strong>quickly</strong>",
|
||||
"body": "blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "blinks <strong>quickly</strong>",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -240,13 +248,15 @@ describe("<EditMessageComposer/>", () => {
|
|||
"body": " * ✨sparkles✨",
|
||||
"msgtype": "m.emote",
|
||||
"m.new_content": {
|
||||
body: "✨sparkles✨",
|
||||
msgtype: "m.emote",
|
||||
"body": "✨sparkles✨",
|
||||
"msgtype": "m.emote",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -264,166 +274,246 @@ describe("<EditMessageComposer/>", () => {
|
|||
"body": " * //dev/null is my favourite place",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
body: "//dev/null is my favourite place",
|
||||
msgtype: "m.text",
|
||||
"body": "//dev/null is my favourite place",
|
||||
"msgtype": "m.text",
|
||||
"m.mentions": {},
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with feature_intentional_mentions enabled", () => {
|
||||
const mockSettings = (mockValues: Record<string, unknown> = {}) => {
|
||||
const defaultMockValues = {
|
||||
feature_intentional_mentions: true,
|
||||
};
|
||||
jest.spyOn(SettingsStore, "getValue")
|
||||
.mockClear()
|
||||
.mockImplementation((settingName) => {
|
||||
return { ...defaultMockValues, ...mockValues }[settingName];
|
||||
});
|
||||
};
|
||||
describe("when message is not a reply", () => {
|
||||
it("should attach an empty mentions object for a message with no mentions", async () => {
|
||||
const editState = new EditorStateTransfer(editedEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " + edit";
|
||||
await editText(editContent);
|
||||
|
||||
beforeEach(() => {
|
||||
mockSettings();
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// both content.mentions and new_content.mentions are empty
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({});
|
||||
});
|
||||
|
||||
describe("when message is not a reply", () => {
|
||||
it("should attach an empty mentions object for a message with no mentions", async () => {
|
||||
const editState = new EditorStateTransfer(editedEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " + edit";
|
||||
await editText(editContent);
|
||||
it("should retain mentions in the original message that are not removed by the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
// Remove charlie from the message
|
||||
const editContent = "{backspace}{backspace}friends";
|
||||
await editText(editContent);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// both content.mentions and new_content.mentions are empty
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({});
|
||||
});
|
||||
|
||||
it("should retain mentions in the original message that are not removed by the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
// Remove charlie from the message
|
||||
const editContent = "{backspace}{backspace}friends";
|
||||
await editText(editContent);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no new mentions were added, so nothing in top level mentions
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// bob is still mentioned, charlie removed
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@bob:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove mentions that are removed by the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
const editContent = "new message!";
|
||||
// clear the original message
|
||||
await editText(editContent, true);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no new mentions were added, so nothing in top level mentions
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// bob is not longer mentioned in the edited message, so empty mentions in new_content
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({});
|
||||
});
|
||||
|
||||
it("should add mentions that were added in the edit", async () => {
|
||||
const editState = new EditorStateTransfer(editedEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " and @d";
|
||||
await editText(editContent);
|
||||
|
||||
// submit autocomplete for mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in the edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should add and remove mentions from the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
// Remove charlie from the message
|
||||
await editText("{backspace}{backspace}");
|
||||
// and replace with @room
|
||||
await editText("@d");
|
||||
// submit autocomplete for @dan mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in the edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
// all mentions in the edited version of the event
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@bob:server.org", "@dan:server.org"],
|
||||
});
|
||||
// no new mentions were added, so nothing in top level mentions
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// bob is still mentioned, charlie removed
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@bob:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
describe("when message is replying", () => {
|
||||
const originalEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@ernie:test",
|
||||
room: roomId,
|
||||
content: { body: "original message", msgtype: "m.text" },
|
||||
event: true,
|
||||
});
|
||||
it("should remove mentions that are removed by the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
const editContent = "new message!";
|
||||
// clear the original message
|
||||
await editText(editContent, true);
|
||||
|
||||
const replyEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bert:test",
|
||||
room: roomId,
|
||||
content: {
|
||||
"body": "reply with plain message",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: originalEvent.getId(),
|
||||
},
|
||||
},
|
||||
"m.mentions": {
|
||||
user_ids: [originalEvent.getSender()!],
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no new mentions were added, so nothing in top level mentions
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// bob is not longer mentioned in the edited message, so empty mentions in new_content
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({});
|
||||
});
|
||||
|
||||
it("should add mentions that were added in the edit", async () => {
|
||||
const editState = new EditorStateTransfer(editedEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " and @d";
|
||||
await editText(editContent);
|
||||
|
||||
// wait for autocompletion to render
|
||||
await screen.findByText("Dan");
|
||||
// submit autocomplete for mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in the edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should add and remove mentions from the edit", async () => {
|
||||
const editState = new EditorStateTransfer(eventWithMentions);
|
||||
getComponent(editState);
|
||||
// Remove charlie from the message
|
||||
await editText("{backspace}{backspace}");
|
||||
// and replace with @room
|
||||
await editText("@d");
|
||||
// wait for autocompletion to render
|
||||
await screen.findByText("Dan");
|
||||
// submit autocomplete for @dan mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in the edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
// all mentions in the edited version of the event
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: ["@bob:server.org", "@dan:server.org"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when message is replying", () => {
|
||||
const originalEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@ernie:test",
|
||||
room: roomId,
|
||||
content: { body: "original message", msgtype: "m.text" },
|
||||
event: true,
|
||||
});
|
||||
|
||||
const replyEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bert:test",
|
||||
room: roomId,
|
||||
content: {
|
||||
"body": "reply with plain message",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: originalEvent.getId(),
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
"m.mentions": {
|
||||
user_ids: [originalEvent.getSender()!],
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const replyWithMentions = mkEvent({
|
||||
const replyWithMentions = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bert:test",
|
||||
room: roomId,
|
||||
content: {
|
||||
"body": 'reply that mentions <a href="https://matrix.to/#/@bob:server.org">Bob</a>',
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: originalEvent.getId(),
|
||||
},
|
||||
},
|
||||
"m.mentions": {
|
||||
user_ids: [
|
||||
// sender of event we replied to
|
||||
originalEvent.getSender()!,
|
||||
// mentions from this event
|
||||
"@bob:server.org",
|
||||
],
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setupRoomWithEventsTimeline(room, [originalEvent, replyEvent]);
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when editing with plain text", async () => {
|
||||
const editState = new EditorStateTransfer(replyEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " + edit";
|
||||
await editText(editContent);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no new mentions from edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when adding a mention", async () => {
|
||||
const editState = new EditorStateTransfer(replyEvent);
|
||||
getComponent(editState);
|
||||
await editText(" and @d");
|
||||
// wait for autocompletion to render
|
||||
await screen.findByText("Dan");
|
||||
// submit autocomplete for @dan mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
// edited reply still mentions the parent event sender
|
||||
// plus new mention @dan
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender(), "@dan:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when removing all mentions from content", async () => {
|
||||
const editState = new EditorStateTransfer(replyWithMentions);
|
||||
getComponent(editState);
|
||||
// replace text to remove all mentions
|
||||
await editText("no mentions here", true);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no mentions in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
// existing @bob mention removed
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when removing mention of said user", async () => {
|
||||
const replyThatMentionsParentEventSender = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bert:test",
|
||||
room: roomId,
|
||||
content: {
|
||||
"body": 'reply that mentions <a href="https://matrix.to/#/@bob:server.org">Bob</a>',
|
||||
"body": `reply that mentions the sender of the message we replied to <a href="https://matrix.to/#/${originalEvent.getSender()!}">Ernie</a>`,
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
|
@ -434,114 +524,25 @@ describe("<EditMessageComposer/>", () => {
|
|||
user_ids: [
|
||||
// sender of event we replied to
|
||||
originalEvent.getSender()!,
|
||||
// mentions from this event
|
||||
"@bob:server.org",
|
||||
],
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
const editState = new EditorStateTransfer(replyThatMentionsParentEventSender);
|
||||
getComponent(editState);
|
||||
// replace text to remove all mentions
|
||||
await editText("no mentions here", true);
|
||||
|
||||
beforeEach(() => {
|
||||
setupRoomWithEventsTimeline(room, [originalEvent, replyEvent]);
|
||||
});
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
it("should retain parent event sender in mentions when editing with plain text", async () => {
|
||||
const editState = new EditorStateTransfer(replyEvent);
|
||||
getComponent(editState);
|
||||
const editContent = " + edit";
|
||||
await editText(editContent);
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no new mentions from edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when adding a mention", async () => {
|
||||
const editState = new EditorStateTransfer(replyEvent);
|
||||
getComponent(editState);
|
||||
await editText(" and @d");
|
||||
// submit autocomplete for @dan mention
|
||||
await editText("{enter}");
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// new mention in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({
|
||||
user_ids: ["@dan:server.org"],
|
||||
});
|
||||
// edited reply still mentions the parent event sender
|
||||
// plus new mention @dan
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender(), "@dan:server.org"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when removing all mentions from content", async () => {
|
||||
const editState = new EditorStateTransfer(replyWithMentions);
|
||||
getComponent(editState);
|
||||
// replace text to remove all mentions
|
||||
await editText("no mentions here", true);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no mentions in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
// existing @bob mention removed
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain parent event sender in mentions when removing mention of said user", async () => {
|
||||
const replyThatMentionsParentEventSender = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bert:test",
|
||||
room: roomId,
|
||||
content: {
|
||||
"body": `reply that mentions the sender of the message we replied to <a href="https://matrix.to/#/${originalEvent.getSender()!}">Ernie</a>`,
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: originalEvent.getId(),
|
||||
},
|
||||
},
|
||||
"m.mentions": {
|
||||
user_ids: [
|
||||
// sender of event we replied to
|
||||
originalEvent.getSender()!,
|
||||
],
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
const editState = new EditorStateTransfer(replyThatMentionsParentEventSender);
|
||||
getComponent(editState);
|
||||
// replace text to remove all mentions
|
||||
await editText("no mentions here", true);
|
||||
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
const messageContent = mockClient.sendMessage.mock.calls[0][2];
|
||||
|
||||
// no mentions in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
// no mentions in edit
|
||||
expect(messageContent["m.mentions"]).toEqual({});
|
||||
// edited reply still mentions the parent event sender
|
||||
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({
|
||||
user_ids: [originalEvent.getSender()],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,31 +15,40 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { render, waitFor, screen, act, fireEvent } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
import {
|
||||
EventType,
|
||||
CryptoApi,
|
||||
TweakName,
|
||||
NotificationCountType,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
EventType,
|
||||
IEventDecryptionResult,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
NotificationCountType,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
TweakName,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
import { IEncryptedEventInfo } from "matrix-js-sdk/src/crypto/api";
|
||||
import { EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api";
|
||||
import { CryptoBackend } from "matrix-js-sdk/src/common-crypto/CryptoBackend";
|
||||
|
||||
import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { flushPromises, getRoomContext, mkEncryptedEvent, mkEvent, mkMessage, stubClient } from "../../../test-utils";
|
||||
import {
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getRoomContext,
|
||||
mkEncryptedEvent,
|
||||
mkEvent,
|
||||
mkMessage,
|
||||
stubClient,
|
||||
} from "../../../test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
|
||||
describe("EventTile", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
|
@ -49,12 +58,22 @@ describe("EventTile", () => {
|
|||
|
||||
// let changeEvent: (event: MatrixEvent) => void;
|
||||
|
||||
function TestEventTile(props: Partial<EventTileProps>) {
|
||||
// const [event] = useState(mxEvent);
|
||||
// Give a way for a test to update the event prop.
|
||||
// changeEvent = setEvent;
|
||||
|
||||
return <EventTile mxEvent={mxEvent} {...props} />;
|
||||
/** wrap the EventTile up in context providers, and with basic properties, as it would be by MessagePanel normally. */
|
||||
function WrappedEventTile(props: {
|
||||
roomContext: IRoomState;
|
||||
eventTilePropertyOverrides?: Partial<EventTileProps>;
|
||||
}) {
|
||||
return (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={props.roomContext}>
|
||||
<EventTile
|
||||
mxEvent={mxEvent}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
{...(props.eventTilePropertyOverrides ?? {})}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function getComponent(
|
||||
|
@ -64,14 +83,7 @@ describe("EventTile", () => {
|
|||
const context = getRoomContext(room, {
|
||||
timelineRenderingType: renderingType,
|
||||
});
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={context}>
|
||||
<TestEventTile {...overrides} />
|
||||
</RoomContext.Provider>
|
||||
,
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
return render(<WrappedEventTile roomContext={context} eventTilePropertyOverrides={overrides} />);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -196,34 +208,15 @@ describe("EventTile", () => {
|
|||
});
|
||||
});
|
||||
describe("Event verification", () => {
|
||||
// data for our stubbed getEventEncryptionInfo: a map from event id to result
|
||||
const eventToEncryptionInfoMap = new Map<string, IEncryptedEventInfo>();
|
||||
|
||||
const TRUSTED_DEVICE = DeviceInfo.fromStorage({}, "TRUSTED_DEVICE");
|
||||
const UNTRUSTED_DEVICE = DeviceInfo.fromStorage({}, "UNTRUSTED_DEVICE");
|
||||
// data for our stubbed getEncryptionInfoForEvent: a map from event id to result
|
||||
const eventToEncryptionInfoMap = new Map<string, EventEncryptionInfo>();
|
||||
|
||||
beforeEach(() => {
|
||||
eventToEncryptionInfoMap.clear();
|
||||
|
||||
// a mocked version of getEventEncryptionInfo which will pick its result from `eventToEncryptionInfoMap`
|
||||
client.getEventEncryptionInfo = (event) => eventToEncryptionInfoMap.get(event.getId()!)!;
|
||||
|
||||
// a mocked version of checkUserTrust which always says the user is trusted (we do our testing via
|
||||
// unverified devices).
|
||||
const trustedUserTrustLevel = new UserTrustLevel(true, true, true);
|
||||
client.checkUserTrust = (_userId) => trustedUserTrustLevel;
|
||||
|
||||
// a version of checkDeviceTrust which says that TRUSTED_DEVICE is trusted, and others are not.
|
||||
const trustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, true, false);
|
||||
const untrustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, false, false);
|
||||
const mockCrypto = {
|
||||
getDeviceVerificationStatus: async (userId: string, deviceId: string) => {
|
||||
if (deviceId === TRUSTED_DEVICE.deviceId) {
|
||||
return trustedDeviceTrustLevel;
|
||||
} else {
|
||||
return untrustedDeviceTrustLevel;
|
||||
}
|
||||
},
|
||||
// a mocked version of getEncryptionInfoForEvent which will pick its result from `eventToEncryptionInfoMap`
|
||||
getEncryptionInfoForEvent: async (event: MatrixEvent) => eventToEncryptionInfoMap.get(event.getId()!)!,
|
||||
} as unknown as CryptoApi;
|
||||
client.getCrypto = () => mockCrypto;
|
||||
});
|
||||
|
@ -236,9 +229,9 @@ describe("EventTile", () => {
|
|||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: UNTRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNSIGNED_DEVICE,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
@ -261,9 +254,9 @@ describe("EventTile", () => {
|
|||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
@ -275,6 +268,67 @@ describe("EventTile", () => {
|
|||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[EventShieldReason.UNKNOWN, "Unknown error"],
|
||||
[EventShieldReason.UNVERIFIED_IDENTITY, "unverified user"],
|
||||
[EventShieldReason.UNSIGNED_DEVICE, "device not verified by its owner"],
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"],
|
||||
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
|
||||
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
|
||||
])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
|
||||
mxEvent = await mkEncryptedEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
plainType: "m.room.message",
|
||||
user: "@alice:example.org",
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: reasonCode,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
|
||||
expect(e2eIcons).toHaveLength(1);
|
||||
expect(e2eIcons[0].classList).toContain("mx_EventTile_e2eIcon_normal");
|
||||
expect(e2eIcons[0].getAttribute("aria-label")).toContain(expectedText);
|
||||
});
|
||||
|
||||
describe("undecryptable event", () => {
|
||||
filterConsole("Error decrypting event");
|
||||
|
||||
it("shows an undecryptable warning", async () => {
|
||||
mxEvent = mkEvent({
|
||||
type: "m.room.encrypted",
|
||||
room: room.roomId,
|
||||
user: "@alice:example.org",
|
||||
event: true,
|
||||
content: {},
|
||||
});
|
||||
|
||||
const mockCrypto = {
|
||||
decryptEvent: async (_ev): Promise<IEventDecryptionResult> => {
|
||||
throw new Error("can't decrypt");
|
||||
},
|
||||
} as CryptoBackend;
|
||||
|
||||
await mxEvent.attemptDecryption(mockCrypto);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_decryption_failure",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the warning when the event is edited", async () => {
|
||||
// we start out with an event from the trusted device
|
||||
mxEvent = await mkEncryptedEvent({
|
||||
|
@ -284,11 +338,13 @@ describe("EventTile", () => {
|
|||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
|
@ -305,13 +361,14 @@ describe("EventTile", () => {
|
|||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(replacementEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: UNTRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNSIGNED_DEVICE,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
await act(async () => {
|
||||
mxEvent.makeReplaced(replacementEvent);
|
||||
flushPromises();
|
||||
rerender(<WrappedEventTile roomContext={roomContext} />);
|
||||
await flushPromises;
|
||||
});
|
||||
|
||||
// check it was updated
|
||||
|
@ -331,12 +388,14 @@ describe("EventTile", () => {
|
|||
user: "@alice:example.org",
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
|
@ -355,7 +414,8 @@ describe("EventTile", () => {
|
|||
|
||||
await act(async () => {
|
||||
mxEvent.makeReplaced(replacementEvent);
|
||||
await flushPromises();
|
||||
rerender(<WrappedEventTile roomContext={roomContext} />);
|
||||
await flushPromises;
|
||||
});
|
||||
|
||||
// check it was updated
|
||||
|
|
|
@ -210,6 +210,10 @@ describe("RoomHeader", () => {
|
|||
});
|
||||
|
||||
it("opens the notifications panel", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
if (name === "feature_notifications") return true;
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<RoomHeader room={room} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.get()!),
|
||||
|
|
|
@ -39,7 +39,6 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
|
|||
import { mockPlatformPeg } from "../../../test-utils/platform";
|
||||
import { doMaybeLocalRoomAction } from "../../../../src/utils/local-room";
|
||||
import { addTextToComposer } from "../../../test-utils/composer";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
jest.mock("../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
|
@ -97,8 +96,9 @@ describe("<SendMessageComposer/>", () => {
|
|||
const content = createMessageContent("@alice:test", model, undefined, undefined, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "hello world",
|
||||
msgtype: "m.text",
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text",
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,10 +110,11 @@ describe("<SendMessageComposer/>", () => {
|
|||
const content = createMessageContent("@alice:test", model, undefined, undefined, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "hello *world*",
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "hello <em>world</em>",
|
||||
"body": "hello *world*",
|
||||
"msgtype": "m.text",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "hello <em>world</em>",
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -125,10 +126,11 @@ describe("<SendMessageComposer/>", () => {
|
|||
const content = createMessageContent("@alice:test", model, undefined, undefined, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "blinks __quickly__",
|
||||
msgtype: "m.emote",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "blinks <strong>quickly</strong>",
|
||||
"body": "blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "blinks <strong>quickly</strong>",
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -141,8 +143,9 @@ describe("<SendMessageComposer/>", () => {
|
|||
const content = createMessageContent("@alice:test", model, undefined, undefined, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "✨sparkles✨",
|
||||
msgtype: "m.emote",
|
||||
"body": "✨sparkles✨",
|
||||
"msgtype": "m.emote",
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -155,23 +158,14 @@ describe("<SendMessageComposer/>", () => {
|
|||
const content = createMessageContent("@alice:test", model, undefined, undefined, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "/dev/null is my favourite place",
|
||||
msgtype: "m.text",
|
||||
"body": "/dev/null is my favourite place",
|
||||
"msgtype": "m.text",
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("attachMentions", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_intentional_mentions",
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReset();
|
||||
});
|
||||
|
||||
const partsCreator = createPartCreator();
|
||||
|
||||
it("no mentions", () => {
|
||||
|
@ -488,8 +482,9 @@ describe("<SendMessageComposer/>", () => {
|
|||
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, {
|
||||
body: "test message",
|
||||
msgtype: MsgType.Text,
|
||||
"body": "test message",
|
||||
"msgtype": MsgType.Text,
|
||||
"m.mentions": {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -507,8 +502,9 @@ describe("<SendMessageComposer/>", () => {
|
|||
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, {
|
||||
body: "test message",
|
||||
msgtype: MsgType.Text,
|
||||
"body": "test message",
|
||||
"msgtype": MsgType.Text,
|
||||
"m.mentions": {},
|
||||
});
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: `effects.confetti` });
|
||||
|
@ -534,8 +530,9 @@ describe("<SendMessageComposer/>", () => {
|
|||
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, {
|
||||
body: "test message",
|
||||
msgtype: MsgType.Text,
|
||||
"body": "test message",
|
||||
"msgtype": MsgType.Text,
|
||||
"m.mentions": {},
|
||||
});
|
||||
|
||||
expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: `effects.confetti` });
|
||||
|
|
|
@ -27,7 +27,6 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
|
|||
import { VoiceRecordingStore } from "../../../../src/stores/VoiceRecordingStore";
|
||||
import { PlaybackClock } from "../../../../src/audio/PlaybackClock";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
jest.mock("../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
|
@ -103,10 +102,6 @@ describe("<VoiceRecordComposerTile/>", () => {
|
|||
return fn(roomId);
|
||||
},
|
||||
);
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_intentional_mentions",
|
||||
);
|
||||
});
|
||||
|
||||
describe("send", () => {
|
||||
|
|
|
@ -60,14 +60,6 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
|
|||
>
|
||||
<div />
|
||||
</button>
|
||||
<button
|
||||
aria-label="Notifications"
|
||||
class="_icon-button_1segd_17"
|
||||
data-state="closed"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
>
|
||||
<div />
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
</DocumentFragment>
|
||||
|
|
|
@ -15,18 +15,11 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsServer,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../../../test-utils";
|
||||
import SdkConfig from "../../../../../../src/SdkConfig";
|
||||
import MatrixClientBackedController from "../../../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
|
||||
describe("<LabsUserSettingsTab />", () => {
|
||||
const sdkConfigSpy = jest.spyOn(SdkConfig, "get");
|
||||
|
@ -36,12 +29,6 @@ describe("<LabsUserSettingsTab />", () => {
|
|||
};
|
||||
const getComponent = () => <LabsUserSettingsTab {...defaultProps} />;
|
||||
|
||||
const userId = "@alice:server.org";
|
||||
const cli = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsServer(),
|
||||
});
|
||||
|
||||
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -73,31 +60,4 @@ describe("<LabsUserSettingsTab />", () => {
|
|||
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
|
||||
expect(labsSections).toHaveLength(9);
|
||||
});
|
||||
|
||||
it("allow setting a labs flag which requires unstable support once support is confirmed", async () => {
|
||||
// enable labs
|
||||
sdkConfigSpy.mockImplementation((configName) => configName === "show_labs_settings");
|
||||
|
||||
const deferred = defer<boolean>();
|
||||
cli.doesServerSupportUnstableFeature.mockImplementation(async (featureName) => {
|
||||
return featureName === "org.matrix.msc3952_intentional_mentions" ? deferred.promise : false;
|
||||
});
|
||||
MatrixClientBackedController.matrixClient = cli;
|
||||
|
||||
const { queryByText } = render(getComponent());
|
||||
|
||||
expect(
|
||||
queryByText("Enable intentional mentions")!
|
||||
.closest(".mx_SettingsFlag")!
|
||||
.querySelector(".mx_AccessibleButton"),
|
||||
).toHaveAttribute("aria-disabled", "true");
|
||||
deferred.resolve(true);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
queryByText("Enable intentional mentions")!
|
||||
.closest(".mx_SettingsFlag")!
|
||||
.querySelector(".mx_AccessibleButton"),
|
||||
).toHaveAttribute("aria-disabled", "false");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue