diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts index 3d62bb946c..43144a34a1 100644 --- a/src/utils/Reply.ts +++ b/src/utils/Reply.ts @@ -18,13 +18,14 @@ import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import sanitizeHtml from "sanitize-html"; import escapeHtml from "escape-html"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; +import { MsgType } from "matrix-js-sdk/src/@types/event"; import { PERMITTED_URL_SCHEMES } from "../HtmlUtils"; import { makeUserPermalink, RoomPermalinkCreator } from "./permalinks/Permalinks"; import { RecursivePartial } from "../@types/common"; import SettingsStore from "../settings/SettingsStore"; -export function getParentEventId(ev: MatrixEvent): string | undefined { +export function getParentEventId(ev?: MatrixEvent): string | undefined { if (!ev || ev.isRedacted()) return; if (ev.replyEventId) { return ev.replyEventId; @@ -70,7 +71,7 @@ export function getNestedReplyText( ): { body: string, html: string } | null { if (!ev) return null; - let { body, formatted_body: html } = ev.getContent(); + let { body, formatted_body: html, msgtype } = ev.getContent(); if (getParentEventId(ev)) { if (body) body = stripPlainReply(body); } @@ -94,9 +95,9 @@ export function getNestedReplyText( const mxid = ev.getSender(); // This fallback contains text that is explicitly EN. - switch (ev.getContent().msgtype) { - case 'm.text': - case 'm.notice': { + switch (msgtype) { + case MsgType.Text: + case MsgType.Notice: { html = `
In reply to ${mxid}` + `
${html}
`; const lines = body.trim().split('\n'); @@ -106,27 +107,27 @@ export function getNestedReplyText( } break; } - case 'm.image': + case MsgType.Image: html = `
In reply to ${mxid}` + `
sent an image.
`; body = `> <${mxid}> sent an image.\n\n`; break; - case 'm.video': + case MsgType.Video: html = `
In reply to ${mxid}` + `
sent a video.
`; body = `> <${mxid}> sent a video.\n\n`; break; - case 'm.audio': + case MsgType.Audio: html = `
In reply to ${mxid}` + `
sent an audio file.
`; body = `> <${mxid}> sent an audio file.\n\n`; break; - case 'm.file': + case MsgType.File: html = `
In reply to ${mxid}` + `
sent a file.
`; body = `> <${mxid}> sent a file.\n\n`; break; - case 'm.emote': { + case MsgType.Emote: { html = `
In reply to * ` + `${mxid}
${html}
`; const lines = body.trim().split('\n'); @@ -173,6 +174,10 @@ export function makeReplyMixIn(ev?: MatrixEvent): RecursivePartial { } export function shouldDisplayReply(event: MatrixEvent): boolean { + if (event.isRedacted()) { + return false; + } + const inReplyTo = event.getWireContent()?.["m.relates_to"]?.["m.in_reply_to"]; if (!inReplyTo) { return false; diff --git a/test/Reply-test.ts b/test/Reply-test.ts new file mode 100644 index 0000000000..54863341cc --- /dev/null +++ b/test/Reply-test.ts @@ -0,0 +1,148 @@ +/* +Copyright 2022 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 './skinned-sdk'; +import { + getNestedReplyText, + getParentEventId, + shouldDisplayReply, + stripHTMLReply, + stripPlainReply, +} from "../src/utils/Reply"; +import { mkEvent } from "./test-utils"; +import { RoomPermalinkCreator } from "../src/utils/permalinks/Permalinks"; + +// don't litter test console with logs +jest.mock("matrix-js-sdk/src/logger"); + +describe("Reply", () => { + describe("getParentEventId", () => { + it("returns undefined if given a falsey value", async () => { + expect(getParentEventId()).toBeUndefined(); + }); + it("returns undefined if given a redacted event", async () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + event.makeRedacted(event); + + expect(getParentEventId(event)).toBeUndefined(); + }); + it("returns undefined if the given event is not a reply", async () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + + expect(getParentEventId(event)).toBeUndefined(); + }); + it("returns id of the event being replied to", async () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: { + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$event1", + }, + }, + }, + }); + + expect(getParentEventId(event)).toBe("$event1"); + }); + }); + + describe("stripPlainReply", () => { + it("Removes leading quotes until the first blank line", () => { + expect(stripPlainReply(` +> This is part +> of the quote + +But this is not + `.trim())).toBe("But this is not"); + }); + }); + + describe("stripHTMLReply", () => { + it("Removes from the input", () => { + expect(stripHTMLReply(` + + This is part + of the quote + + But this is not + `).trim()).toBe("But this is not"); + }); + }); + + describe("getNestedReplyText", () => { + it("Returns valid reply fallback text for m.text msgtypes", () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: { + body: "body", + msgtype: "m.text", + }, + }); + + expect(getNestedReplyText(event, { + forEvent(eventId: string): string { + return "$$permalink$$"; + }, + } as RoomPermalinkCreator)).toMatchSnapshot(); + }); + }); + + describe("shouldDisplayReply", () => { + it("Returns false for redacted events", () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + event.makeRedacted(event); + + expect(shouldDisplayReply(event)).toBe(false); + }); + + it("Returns false for non-reply events", () => { + const event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + + expect(shouldDisplayReply(event)).toBe(false); + }); + }); +}); diff --git a/test/__snapshots__/Reply-test.ts.snap b/test/__snapshots__/Reply-test.ts.snap new file mode 100644 index 0000000000..df1a11eeb5 --- /dev/null +++ b/test/__snapshots__/Reply-test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Reply getNestedReplyText Returns valid reply fallback text for m.text msgtypes 1`] = ` +Object { + "body": "> <@user1:server> body + +", + "html": "
In reply to @user1:server
body
", +} +`;