Files
RenewCMS/test/markdown_converter_test.go
T

90 lines
5.7 KiB
Go

package test
import (
domainArticle "RenewCMS/internal/domain/article"
"RenewCMS/internal/infrastructure/markdown"
"errors"
"testing"
)
func TestMarkdownConverterFidelity(t *testing.T) {
converter := markdown.NewConverter()
examples := []struct {
name, html, markdown string
}{
{"headings", "<h1>T</h1><h3>S</h3>", "# T\n\n### S"},
{"bold and italic", "<p><strong>b</strong> <em>i</em></p>", "**b** *i*"},
{"link", `<p><a href="https://example.com/x">link</a></p>`, "[link](https://example.com/x)"},
{"bullet list", "<ul><li><p>a</p></li><li><p>b</p></li></ul>", "- a\n- b"},
{"ordered list", "<ol><li><p>a</p></li><li><p>b</p></li></ol>", "1. a\n2. b"},
{"data-indent dropped", `<ul><li data-indent="2"><p>a</p></li></ul>`, "- a"},
{"nested list", "<ul><li><p>a</p><ul><li><p>n</p></li></ul></li></ul>", "- a\n - n"},
{"blockquote", "<blockquote><p>q</p></blockquote>", "> q"},
{"code block", `<pre><code class="language-go">x := 1</code></pre>`, "```go\nx := 1\n```"},
{"rule", "<hr>", "---"},
{"image", `<img src="/static/uploadedImages/a.png" alt="A">`, "![A](/static/uploadedImages/a.png)"},
{"script and style removed", "<p>ok</p><script>alert(1)</script><style>p{}</style>", "ok"},
{"unsupported span stripped", `<p>x<span style="color:red">y</span></p>`, "xy"},
{"inline code", "<p>use <code>go test</code></p>", "use `go test`"},
{"empty body", "", ""},
{"line break", "<p>a<br>b</p>", "a\\\nb"},
{"literal angle bracket is escaped", "<p>&lt;script&gt;</p>", "\\<script>"},
{"javascript link dropped", `<p><a href="javascript:alert(1)">x</a></p>`, "x"},
{"bold keeps outer spaces", "<p>a<strong> b </strong>c</p>", "a **b** c"},
{"loose list item", "<ul><li><p>a</p><p>b</p></li></ul>", "- a\n\n b"},
{"inline code with edge backtick", "<p><code>`x</code></p>", "`` `x ``"},
{"code block keeps br and drops script", "<pre><code>a<br>b<script>z</script></code></pre>", "```\na\nb\n```"},
{"adjacent same marks merge", "<p><strong>a</strong><strong>b</strong></p>", "**ab**"},
{"adjacent different marks stay apart", "<p><strong>a</strong><em>b</em></p>", "**a***b*"},
{"two paragraphs", "<p>a</p><p>b</p>", "a\n\nb"},
{"centered paragraph", `<p style="text-align: center">hi <strong>b</strong></p>`, "<div style=\"text-align: center\">\n\nhi **b**\n\n</div>"},
{"right aligned heading", `<h2 style="text-align:right">T</h2>`, "<div style=\"text-align: right\">\n\n## T\n\n</div>"},
{"justified among other styles", `<p style="color:red; TEXT-ALIGN: Justify">a</p>`, "<div style=\"text-align: justify\">\n\na\n\n</div>"},
{"tiptap style with trailing semicolon", `<p style="text-align: center;">a</p>`, "<div style=\"text-align: center\">\n\na\n\n</div>"},
{"tiptap explicit left", `<p style="text-align: left;">a</p>`, "a"},
{"left alignment is the default", `<p style="text-align: left">a</p>`, "a"},
{"unknown alignment ignored", `<p style="text-align: start">a</p>`, "a"},
{"style without value ignored", `<p style="text-align">a</p>`, "a"},
{"empty aligned paragraph", `<p style="text-align: center"></p>`, ""},
{"aligned paragraph in list item", `<ul><li><p style="text-align: center">a</p></li></ul>`, "- <div style=\"text-align: center\">\n\n a\n\n </div>"},
{"aligned paragraph in blockquote", `<blockquote><p style="text-align: center">q</p></blockquote>`, "> <div style=\"text-align: center\">\n>\n> q\n>\n> </div>"},
{"alignment skips the image inside a paragraph", `<p style="text-align: center">x<img src="/i.png" alt="I">y</p>`, "<div style=\"text-align: center\">\n\nx\n\n</div>\n\n![I](/i.png)\n\n<div style=\"text-align: center\">\n\ny\n\n</div>"},
{"text characters are escaped", "<p>a * b _c_ `d` [e]</p>", "a \\* b \\_c\\_ \\`d\\` \\[e\\]"},
{"leading hash is escaped", "<p># not a heading</p>", "\\# not a heading"},
{"hash without space is kept", "<p>#tag</p>", "#tag"},
{"leading dash is escaped", "<p>- not a list</p>", "\\- not a list"},
{"leading plus is escaped", "<p>+ not a list</p>", "\\+ not a list"},
{"leading quote marker is escaped", "<p>&gt; not a quote</p>", "\\> not a quote"},
{"leading number is escaped", "<p>1. not a list</p>", "1\\. not a list"},
{"leading number with parenthesis is escaped", "<p>2) not a list</p>", "2\\) not a list"},
{"leading rule is escaped", "<p>---</p>", "\\---"},
{"leading equals is escaped", "<p>==</p>", "\\=="},
{"inner dash is kept", "<p>a - b</p>", "a - b"},
{"marker after a line break is escaped", "<p>a<br>- b</p>", "a\\\n\\- b"},
{"combined document", `<h1>Title</h1><p style="text-align: center">Intro <strong>bold</strong> and <em>it</em></p><ul><li><p>one</p></li><li><p>two</p></li></ul><img src="/static/uploadedImages/a.png" alt="A"><pre><code class="language-go">x := 1</code></pre><blockquote><p>quote</p></blockquote><hr>`, "# Title\n\n<div style=\"text-align: center\">\n\nIntro **bold** and *it*\n\n</div>\n\n- one\n- two\n\n![A](/static/uploadedImages/a.png)\n\n```go\nx := 1\n```\n\n> quote\n\n---"},
}
for _, example := range examples {
t.Run(example.name, func(t *testing.T) {
got, err := converter.Render(example.html, domainArticle.BodyFormatMarkdown)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != example.markdown {
t.Errorf("expected %q, got %q", example.markdown, got)
}
})
}
}
func TestMarkdownConverterHtmlAndUnknownFormat(t *testing.T) {
converter := markdown.NewConverter()
html := "<p>a</p><script>x</script>"
got, err := converter.Render(html, domainArticle.BodyFormatHTML)
if err != nil || got != html {
t.Errorf("html must pass through unchanged, got %q, %v", got, err)
}
if _, err := converter.Render(html, domainArticle.BodyFormat("pdf")); !errors.Is(err, domainArticle.ErrUnsupportedBodyFormat) {
t.Errorf("expected ErrUnsupportedBodyFormat, got %v", err)
}
}