package web
import (
"strings"
"testing"
)
func TestRenderMarkdownSupportsGFM(t *testing.T) {
source := `# Heading
Text with **bold**, _italic_, [a link](https://example.com), and .
Inline code: ` + "`**not bold**`" + `
` + "```go\n**also not bold**\n```"
renderedHTML, err := renderMarkdown(source, "/demo/blob/main/docs/", "/demo/raw/blob/main/docs/")
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
for _, want := range []string{
`<h1 id="heading">Heading</h1>`,
`<strong>bold</strong>`,
`<em>italic</em>`,
`<a href="https://example.com">a link</a>`,
`<img src="/demo/raw/blob/main/docs/image.png" alt="an image">`,
`<code>**not bold**</code>`,
"<pre><code class=\"language-go\">**also not bold**\n</code></pre>",
} {
if !strings.Contains(rendered, want) {
t.Fatalf("rendered Markdown did not contain %q\noutput:\n%s", want, rendered)
}
}
if strings.Contains(rendered, `<strong>not bold</strong>`) || strings.Contains(rendered, `<strong>also not bold</strong>`) {
t.Fatalf("Markdown inside code was parsed:\n%s", rendered)
}
}
func TestRenderMarkdownUsesSafeRenderer(t *testing.T) {
source := `<script>alert("x")</script>
[unsafe](javascript:alert('x'))
)`
renderedHTML, err := renderMarkdown(source, "/demo/blob/main/", "/demo/raw/blob/main/")
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
if strings.Contains(rendered, "<script>") || strings.Contains(rendered, `href="javascript:`) || strings.Contains(rendered, `src="javascript:`) {
t.Fatalf("rendered Markdown contained unsafe HTML:\n%s", rendered)
}
}
func TestRenderMarkdownResolvesRelativeLinksAgainstPreviewDirectory(t *testing.T) {
renderedHTML, err := renderMarkdown(
`[guide](../guide.md#usage) `,
"/demo/blob/main/docs/reference/",
"/demo/raw/blob/main/docs/reference/",
)
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
for _, want := range []string{
`href="/demo/blob/main/docs/guide.md#usage"`,
`src="/demo/raw/blob/main/docs/reference/images/logo.png"`,
} {
if !strings.Contains(rendered, want) {
t.Fatalf("rendered Markdown did not contain %q\noutput:\n%s", want, rendered)
}
}
}
func TestRenderMarkdownSupportsInlineAndDisplayMath(t *testing.T) {
source := `Euler wrote $e^{i\pi} + 1 = 0$.
$$
\int_0^1 x^2 \, dx = \frac{1}{3}
$$`
renderedHTML, err := renderMarkdown(source, "/demo/blob/main/", "/demo/raw/blob/main/")
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
for _, want := range []string{
`<span class="math-inline">e^{i\pi} + 1 = 0</span>`,
"<span class=\"math-display\">\n\\int_0^1 x^2 \\, dx = \\frac{1}{3}\n</span>",
} {
if !strings.Contains(rendered, want) {
t.Fatalf("rendered Markdown did not contain %q\noutput:\n%s", want, rendered)
}
}
}
func TestRenderMarkdownMathDoesNotConsumeCodeOrCurrency(t *testing.T) {
source := "A ticket costs $10 and a meal costs $20. `code $x$`\n\n```tex\n$y$\n```"
renderedHTML, err := renderMarkdown(source, "/demo/blob/main/", "/demo/raw/blob/main/")
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
if strings.Contains(rendered, `class="math-inline"`) || strings.Contains(rendered, `class="math-display"`) {
t.Fatalf("currency or code was parsed as math:\n%s", rendered)
}
for _, want := range []string{"$10", "$20", "<code>code $x$</code>", "<pre><code class=\"language-tex\">$y$"} {
if !strings.Contains(rendered, want) {
t.Fatalf("rendered Markdown did not contain %q\noutput:\n%s", want, rendered)
}
}
}
func TestRenderMarkdownLeavesUnclosedMathAsText(t *testing.T) {
renderedHTML, err := renderMarkdown(`An unfinished $x + 1 expression.`, "/demo/blob/main/", "/demo/raw/blob/main/")
if err != nil {
t.Fatal(err)
}
rendered := string(renderedHTML)
if strings.Contains(rendered, `class="math-inline"`) || !strings.Contains(rendered, `$x + 1`) {
t.Fatalf("unclosed math was not preserved as text:\n%s", rendered)
}
}