From 2649c6fd7557cde13f0d852fcc9696effb19435a Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Fri, 27 May 2022 20:10:22 +0100 Subject: [PATCH] Tests for roundtrips md<->HTML (#8696) * Tests for roundtrips md<->HTML * Convert roundtrip tests to it.each table style * Fix lint errors * Remove extraneous "f " Co-authored-by: Travis Ralston * Test __ -> ** * Test for code blocks containing markdown * Test for more ordered lists * Test for code blocks with language specifiers * Additional cases for HTML->markdown->HTML Co-authored-by: Travis Ralston --- test/editor/roundtrip-test.ts | 170 ++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 test/editor/roundtrip-test.ts diff --git a/test/editor/roundtrip-test.ts b/test/editor/roundtrip-test.ts new file mode 100644 index 0000000000..424f639233 --- /dev/null +++ b/test/editor/roundtrip-test.ts @@ -0,0 +1,170 @@ +/* +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 { MatrixEvent } from 'matrix-js-sdk/src/matrix'; + +import { parseEvent } from "../../src/editor/deserialize"; +import EditorModel from '../../src/editor/model'; +import DocumentOffset from '../../src/editor/offset'; +import { htmlSerializeIfNeeded, textSerialize } from '../../src/editor/serialize'; +import { createPartCreator } from "./mock"; + +function htmlMessage(formattedBody: string, msgtype = "m.text") { + return { + getContent() { + return { + msgtype, + format: "org.matrix.custom.html", + formatted_body: formattedBody, + }; + }, + } as unknown as MatrixEvent; +} + +async function md2html(markdown: string): Promise { + const pc = createPartCreator(); + const oldModel = new EditorModel([], pc, () => {}); + await oldModel.update( + markdown, + "insertText", + new DocumentOffset(markdown.length, false), + ); + return htmlSerializeIfNeeded(oldModel, { forceHTML: true }); +} + +function html2md(html: string): string { + const pc = createPartCreator(); + const parts = parseEvent(htmlMessage(html), pc); + const newModel = new EditorModel(parts, pc); + return textSerialize(newModel); +} + +async function roundTripMarkdown(markdown: string): Promise { + return html2md(await md2html(markdown)); +} + +async function roundTripHtml(html: string): Promise { + return await md2html(html2md(html)); +} + +describe('editor/roundtrip', function() { + describe('markdown messages should round-trip if they contain', function() { + test.each([ + ["newlines", "hello\nworld"], + ["pills", "text message for @room"], + ["pills with interesting characters in mxid", "text message for @alice\\\\\\_\\]#>&:hs.example.com"], + ["styling", "**bold** and _emphasised_"], + ["bold within a word", "abso**fragging**lutely"], + ["escaped html", "a\\b"], + ["escaped markdown", "\\*\\*foo\\*\\* \\_bar\\_ \\[a\\](b)"], + ["escaped backslashes", "C:\\\\Program Files"], + ["code in backticks", "foo ->`x`"], + ["code blocks containing backticks", "```\nfoo ->`x`\nbar\n```"], + ["code blocks containing markdown", "```\n__init__.py\n```"], + ["nested formatting", "ab **c _d_ e** fg"], + ["an ordered list", "A\n\n1. b\n2. c\n3. d\nE"], + ["an ordered list starting later", "A\n\n9. b\n10. c\n11. d\nE"], + ["an unordered list", "A\n\n- b\n- c\n- d\nE"], + ["code block followed by text after a blank line", "```A\nfoo(bar).baz();\n\n3\n```\n\nB"], + ["just a code block", "```\nfoo(bar).baz();\n\n3\n```"], + ["code block with language specifier", "```bash\nmake install\n\n```"], + ["inline code", "there's no place `127.0.0.1` like"], + ["nested quotations", "saying\n\n> > foo\n\n> NO\n\nis valid"], + ["quotations", "saying\n\n> NO\n\nis valid"], + ["links", "click [this](http://example.com/)!"], + ])('%s', async (_name, markdown) => { + expect(await roundTripMarkdown(markdown)).toEqual(markdown); + }); + + test.skip.each([ + // Removes trailing spaces + ["a code block followed by newlines", "```\nfoo(bar).baz();\n\n3\n```\n\n"], + // Adds a space after the code block + ["a code block surrounded by text", "```A\nfoo(bar).baz();\n\n3\n```\nB"], + // Adds a space before the list + ["an unordered list directly preceded by text", "A\n- b\n- c\n- d\nE"], + // Re-numbers to 1, 2, 3 + ["an ordered list where everything is 1", "A\n\n1. b\n1. c\n1. d\nE"], + // Adds a space before the list + ["an ordered list directly preceded by text", "A\n1. b\n2. c\n3. d\nE"], + // Adds and removes spaces before the nested list + ["nested unordered lists", "A\n- b\n- c\n - c1\n - c2\n- d\nE"], + // Adds and removes spaces before the nested list + ["nested ordered lists", "A\n\n1. b\n2. c\n 1. c1\n 2. c2\n3. d\nE"], + // Adds and removes spaces before the nested list + ["nested mixed lists", "A\n\n1. b\n2. c\n - c1\n - c2\n3. d\nE"], + // Backslashes get doubled + ["backslashes", "C:\\Program Files"], + // Deletes the whitespace + ['newlines with trailing and leading whitespace', "hello \n world"], + // Escapes the underscores + ["underscores within a word", "abso_fragging_lutely"], + // Includes the trailing text into the quotation + // https://github.com/vector-im/element-web/issues/22341 + ["quotations without separating newlines", "saying\n> NO\nis valid"], + // Removes trailing and leading whitespace + ["quotations with trailing and leading whitespace", "saying \n\n> NO\n\n is valid"], + ])('%s', async (_name, markdown) => { + expect(await roundTripMarkdown(markdown)).toEqual(markdown); + }); + + it('styling, but * becomes _ and __ becomes **', async function() { + expect(await roundTripMarkdown("__bold__ and *emphasised*")) + .toEqual("**bold** and _emphasised_"); + }); + }); + + describe('HTML messages should round-trip if they contain', function() { + test.each([ + ["backslashes", "C:\\Program Files"], + [ + "nested blockquotes", + "
\n

foo

\n
\n

bar

\n
\n
\n", + ], + ["ordered lists", "
    \n
  1. asd
  2. \n
  3. fgd
  4. \n
\n"], + ["ordered lists starting later", '
    \n
  1. asd
  2. \n
  3. fgd
  4. \n
\n'], + ["unordered lists", "
    \n
  • asd
  • \n
  • fgd
  • \n
\n"], + ["code blocks with surrounding text", "

a

\n
a\ny;\n
\n

b

\n"], + ["code blocks", "
a\ny;\n
\n"], + ["code blocks containing markdown", "
__init__.py\n
\n"], + ["code blocks with language specifier", "
__init__.py\n
\n"], + ["paragraphs including formatting", "

one

\n

t w o

\n"], + ["paragraphs", "

one

\n

two

\n"], + ["links", "http://more.example.com/"], + ["escaped html", "This >em<isn't>em< important"], + ["markdown-like symbols", "You _would_ **type** [a](http://this.example.com) this."], + ["formatting within a word", "absofragginglutely"], + ["formatting", "This is important"], + ["line breaks", "one
two"], + ])('%s', async (_name, html) => { + expect(await roundTripHtml(html)).toEqual(html); + }); + + test.skip.each([ + // Strips out the pill - maybe needs some user lookup to work? + ["user pills", 'Alice'], + // Appends a slash to the URL + // https://github.com/vector-im/element-web/issues/22342 + ["links without trailing slashes", 'Go here to see more'], + // Inserts newlines after tags + ["paragraphs without newlines", "

one

two

"], + // Inserts a code block + ["nested lists", "
    \n
  1. asd
  2. \n
  3. \n
      \n
    • fgd
    • \n
    • sdf
    • \n
    \n
  4. \n
\n"], + ])('%s', async (_name, html) => { + expect(await roundTripHtml(html)).toEqual(html); + }); + }); +});