mirror of https://github.com/vector-im/riot-web
438 lines
23 KiB
TypeScript
438 lines
23 KiB
TypeScript
|
/*
|
||
|
Copyright 2024 New Vector Ltd.
|
||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||
|
|
||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||
|
Please see LICENSE files in the repository root for full details.
|
||
|
*/
|
||
|
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||
|
|
||
|
import { parseEvent } from "../../../src/editor/deserialize";
|
||
|
import { Part } from "../../../src/editor/parts";
|
||
|
import { createPartCreator } from "./mock";
|
||
|
|
||
|
const FOUR_SPACES = " ".repeat(4);
|
||
|
|
||
|
function htmlMessage(formattedBody: string, msgtype = "m.text") {
|
||
|
return {
|
||
|
getContent() {
|
||
|
return {
|
||
|
msgtype,
|
||
|
format: "org.matrix.custom.html",
|
||
|
formatted_body: formattedBody,
|
||
|
};
|
||
|
},
|
||
|
} as unknown as MatrixEvent;
|
||
|
}
|
||
|
|
||
|
function textMessage(body: string, msgtype = "m.text") {
|
||
|
return {
|
||
|
getContent() {
|
||
|
return {
|
||
|
msgtype,
|
||
|
body,
|
||
|
};
|
||
|
},
|
||
|
} as unknown as MatrixEvent;
|
||
|
}
|
||
|
|
||
|
function textMessageReply(body: string, msgtype = "m.text") {
|
||
|
return {
|
||
|
...textMessage(body, msgtype),
|
||
|
replyEventId: "!foo:bar",
|
||
|
} as unknown as MatrixEvent;
|
||
|
}
|
||
|
|
||
|
function mergeAdjacentParts(parts: Part[]) {
|
||
|
let prevPart: Part | undefined;
|
||
|
for (let i = 0; i < parts.length; ++i) {
|
||
|
let part: Part | undefined = parts[i];
|
||
|
const isEmpty = !part.text.length;
|
||
|
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
|
||
|
if (isEmpty || isMerged) {
|
||
|
// remove empty or merged part
|
||
|
part = prevPart;
|
||
|
parts.splice(i, 1);
|
||
|
//repeat this index, as it's removed now
|
||
|
--i;
|
||
|
}
|
||
|
prevPart = part;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function normalize(parts: Part[]) {
|
||
|
// merge adjacent parts as this will happen
|
||
|
// in the model anyway, and whether 1 or multiple
|
||
|
// plain parts are returned is an implementation detail
|
||
|
mergeAdjacentParts(parts);
|
||
|
// convert to data objects for easier asserting
|
||
|
return parts.map((p) => p.serialize());
|
||
|
}
|
||
|
|
||
|
describe("editor/deserialize", function () {
|
||
|
describe("text messages", function () {
|
||
|
it("test with newlines", function () {
|
||
|
const parts = normalize(parseEvent(textMessage("hello\nworld"), createPartCreator()));
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "world" });
|
||
|
expect(parts.length).toBe(3);
|
||
|
});
|
||
|
it("@room pill", function () {
|
||
|
const parts = normalize(parseEvent(textMessage("text message for @room"), createPartCreator()));
|
||
|
expect(parts.length).toBe(2);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "text message for " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "at-room-pill", text: "@room" });
|
||
|
});
|
||
|
it("emote", function () {
|
||
|
const text = "says DON'T SHOUT!";
|
||
|
const parts = normalize(parseEvent(textMessage(text, "m.emote"), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "/me says DON'T SHOUT!" });
|
||
|
});
|
||
|
it("spoiler", function () {
|
||
|
const parts = normalize(parseEvent(textMessage("/spoiler broiler"), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "/spoiler broiler" });
|
||
|
});
|
||
|
});
|
||
|
describe("html messages", function () {
|
||
|
it("inline styling", function () {
|
||
|
const html = "<strong>bold</strong> and <em>emphasized</em> text";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "**bold** and _emphasized_ text" });
|
||
|
});
|
||
|
it("hyperlink", function () {
|
||
|
const html = 'click <a href="http://example.com/">this</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "click [this](http://example.com/)!" });
|
||
|
});
|
||
|
it("multiple lines with paragraphs", function () {
|
||
|
const html = "<p>hello</p><p>world</p>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(4);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "plain", text: "world" });
|
||
|
});
|
||
|
it("multiple lines with line breaks", function () {
|
||
|
const html = "hello<br>world";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "world" });
|
||
|
});
|
||
|
it("multiple lines mixing paragraphs and line breaks", function () {
|
||
|
const html = "<p>hello<br>warm</p><p>world</p>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(6);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "warm" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[5]).toStrictEqual({ type: "plain", text: "world" });
|
||
|
});
|
||
|
it("quote", function () {
|
||
|
const html = "<blockquote><p><em>wise</em><br><strong>words</strong></p></blockquote><p>indeed</p>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(6);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "> _wise_" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "> **words**" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[5]).toStrictEqual({ type: "plain", text: "indeed" });
|
||
|
});
|
||
|
it("user pill", function () {
|
||
|
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice", resourceId: "@alice:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
|
||
|
});
|
||
|
it("user pill with displayname containing backslash", function () {
|
||
|
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice\\</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice\\", resourceId: "@alice:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
|
||
|
});
|
||
|
it("user pill with displayname containing opening square bracket", function () {
|
||
|
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice[[</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice[[", resourceId: "@alice:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
|
||
|
});
|
||
|
it("user pill with displayname containing closing square bracket", function () {
|
||
|
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice]</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice]", resourceId: "@alice:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
|
||
|
});
|
||
|
it("user pill with displayname containing linebreak", function () {
|
||
|
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice<br>123</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice123", resourceId: "@alice:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
|
||
|
});
|
||
|
it("room pill", function () {
|
||
|
const html = 'Try <a href="https://matrix.to/#/#room:hs.tld">#room:hs.tld</a>?';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(3);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "Try " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "room-pill", text: "#room:hs.tld", resourceId: "#room:hs.tld" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "?" });
|
||
|
});
|
||
|
it("@room pill", function () {
|
||
|
const html = "<em>formatted</em> message for @room";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(2);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "_formatted_ message for " });
|
||
|
expect(parts[1]).toStrictEqual({ type: "at-room-pill", text: "@room" });
|
||
|
});
|
||
|
it("inline code", function () {
|
||
|
const html = "there is no place like <code>127.0.0.1</code>!";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "there is no place like `127.0.0.1`!" });
|
||
|
});
|
||
|
it("code block with no trailing text", function () {
|
||
|
const html = "<pre><code>0xDEADBEEF\n</code></pre>\n";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "```" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "0xDEADBEEF" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: "```" });
|
||
|
});
|
||
|
// failing likely because of https://github.com/vector-im/element-web/issues/10316
|
||
|
it.skip("code block with no trailing text and no newlines", function () {
|
||
|
const html = "<pre><code>0xDEADBEEF</code></pre>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "```" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "0xDEADBEEF" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: "```" });
|
||
|
});
|
||
|
it("unordered lists", function () {
|
||
|
const html = "<ul><li>Oak</li><li>Spruce</li><li>Birch</li></ul>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "- Oak" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "- Spruce" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: "- Birch" });
|
||
|
});
|
||
|
it("ordered lists", function () {
|
||
|
const html = "<ol><li>Start</li><li>Continue</li><li>Finish</li></ol>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Start" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: "2. Continue" });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: "3. Finish" });
|
||
|
});
|
||
|
it("nested unordered lists", () => {
|
||
|
const html = "<ul><li>Oak<ul><li>Spruce<ul><li>Birch</li></ul></li></ul></li></ul>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "- Oak" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}- Spruce` });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}- Birch` });
|
||
|
});
|
||
|
it("nested ordered lists", () => {
|
||
|
const html = "<ol><li>Oak<ol><li>Spruce<ol><li>Birch</li></ol></li></ol></li></ol>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Oak" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}1. Spruce` });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` });
|
||
|
});
|
||
|
it("nested lists", () => {
|
||
|
const html = "<ol><li>Oak\n<ol><li>Spruce\n<ol><li>Birch</li></ol></li></ol></li></ol>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(5);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Oak\n" });
|
||
|
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}1. Spruce\n` });
|
||
|
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||
|
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` });
|
||
|
});
|
||
|
it("mx-reply is stripped", function () {
|
||
|
const html = "<mx-reply>foo</mx-reply>bar";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "bar" });
|
||
|
});
|
||
|
it("emote", function () {
|
||
|
const html = "says <em>DON'T SHOUT</em>!";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html, "m.emote"), createPartCreator()));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "/me says _DON'T SHOUT_!" });
|
||
|
});
|
||
|
it("spoiler", function () {
|
||
|
const parts = normalize(
|
||
|
parseEvent(htmlMessage("<span data-mx-spoiler>broiler</span>"), createPartCreator()),
|
||
|
);
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({ type: "plain", text: "/spoiler broiler" });
|
||
|
});
|
||
|
it("preserves nested quotes", () => {
|
||
|
const html = "<blockquote>foo<blockquote>bar</blockquote></blockquote>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("surrounds lists with newlines", () => {
|
||
|
const html = "foo<ul><li>bar</li></ul>baz";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("preserves nested formatting", () => {
|
||
|
const html = "a<sub>b<em>c<strong>d<u>e</u></strong></em></sub>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes backticks in code blocks", () => {
|
||
|
const html =
|
||
|
"<p><code>this → ` is a backtick</code></p>" + "<pre><code>and here are 3 of them:\n```</code></pre>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes backticks outside of code blocks", () => {
|
||
|
const html = "some `backticks`";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes backslashes", () => {
|
||
|
const html = "C:\\My Documents";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes asterisks", () => {
|
||
|
const html = "*hello*";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes underscores", () => {
|
||
|
const html = "__emphasis__";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes square brackets", () => {
|
||
|
const html = "[not an actual link](https://example.org)";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
it("escapes angle brackets", () => {
|
||
|
const html = "> \\<del>no formatting here\\</del>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||
|
expect(parts).toMatchSnapshot();
|
||
|
});
|
||
|
});
|
||
|
describe("plaintext messages", function () {
|
||
|
it("turns html tags back into markdown", function () {
|
||
|
const html = '<strong>bold</strong> and <em>emphasized</em> text <a href="http://example.com/">this</a>!';
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "**bold** and _emphasized_ text [this](http://example.com/)!",
|
||
|
});
|
||
|
});
|
||
|
it("keeps backticks unescaped", () => {
|
||
|
const html = "this → ` is a backtick and here are 3 of them:\n```";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "this → ` is a backtick and here are 3 of them:\n```",
|
||
|
});
|
||
|
});
|
||
|
it("keeps backticks outside of code blocks", () => {
|
||
|
const html = "some `backticks`";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "some `backticks`",
|
||
|
});
|
||
|
});
|
||
|
it("keeps backslashes", () => {
|
||
|
const html = "C:\\My Documents";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "C:\\My Documents",
|
||
|
});
|
||
|
});
|
||
|
it("keeps asterisks", () => {
|
||
|
const html = "*hello*";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "*hello*",
|
||
|
});
|
||
|
});
|
||
|
it("keeps underscores", () => {
|
||
|
const html = "__emphasis__";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "__emphasis__",
|
||
|
});
|
||
|
});
|
||
|
it("keeps square brackets", () => {
|
||
|
const html = "[not an actual link](https://example.org)";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "[not an actual link](https://example.org)",
|
||
|
});
|
||
|
});
|
||
|
it("escapes angle brackets", () => {
|
||
|
const html = "> <del>no formatting here</del>";
|
||
|
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "> <del>no formatting here</del>",
|
||
|
});
|
||
|
});
|
||
|
it("strips plaintext replies", () => {
|
||
|
const body = "> Sender: foo\n\nMessage";
|
||
|
const parts = normalize(parseEvent(textMessageReply(body), createPartCreator(), { shouldEscape: false }));
|
||
|
expect(parts.length).toBe(1);
|
||
|
expect(parts[0]).toStrictEqual({
|
||
|
type: "plain",
|
||
|
text: "Message",
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|