clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / tinyclient / markdown_test.go
markdown_test.go 5.5K
1package tinyclient23import (4 "net/url"5 "strings"6 "testing"7)89func TestMarkdownRendersCommonFormsAndEscapesTheRest(t *testing.T) {10 got := string(renderMarkdown("# Title\n\nA `code` span with **bold** and *em* and [a link](https://example.com).\n\n```sh\ngo build <x>\n```\n\n- one\n- two\n\n1. first\n2. second\n\n<script>alert(1)</script> and [bad](javascript:alert(1))"))11 for _, want := range []string{"<h1>Title</h1>", "<code>code</code>", "<strong>bold</strong>", "<em>em</em>", `<a href="https://example.com">a link</a>`, `<pre><code data-lang="sh">go build &lt;x&gt;</code></pre>`, "<ul><li>one</li><li>two</li></ul>", "<ol><li>first</li><li>second</li></ol>", "&lt;script&gt;alert(1)&lt;/script&gt;"} {12 if !strings.Contains(got, want) {13 t.Errorf("missing %q in %s", want, got)14 }15 }16 if strings.Contains(got, "<script>") || strings.Contains(got, `href="javascript:`) {17 t.Fatalf("unsafe markup leaked: %s", got)18 }19}2021func TestRepositoryMarkdownResolvesRelativeLinks(t *testing.T) {22 query := url.Values{"owner": {"alice"}, "repo": {"notes"}, "ref": {"refs/heads/main"}}23 got := string(renderRepositoryMarkdown("[guide](docs/guide.md) [up](../secret.md) [site](https://example.com)", query))24 if !strings.Contains(got, `href="/repos/alice/notes/file/docs/guide.md?ref=refs%2Fheads%2Fmain"`) {25 t.Fatalf("relative link was not mapped to repository viewer: %s", got)26 }27 if !strings.Contains(got, `href="https://example.com"`) {28 t.Fatalf("repository link handling changed unsafe or external links: %s", got)29 }30}3132func TestRepositoryMarkdownDecodesPathsAndPreservesFragments(t *testing.T) {33 query := url.Values{"owner": {"alice"}, "repo": {"notes"}, "ref": {"refs/heads/main"}}34 got := string(renderRepositoryMarkdown("[guide](docs/caf%C3%A9%20guide.md?download=1#L2)", query))35 want := `href="/repos/alice/notes/file/docs/caf%C3%A9%20guide.md?download=1&amp;ref=refs%2Fheads%2Fmain#L2"`36 if !strings.Contains(got, want) {37 t.Fatalf("encoded README link lost its resource or fragment: %s", got)38 }39}4041func TestChatMarkdownBreaksLinesAndAutolinksOutsideLinksAndCode(t *testing.T) {42 got := string(renderChatMarkdown("It works: **[the page](https://012.run/wiki/agents)**.\nsee https://example.com/x, and nostr:npub1ttrypewl3au52wqux86r22yt506c077k3maj02a0jste97wrvd5sjfutc2\n\n- `code https://not.a.link`\n- [x](javascript:alert(1))\n\n```\nhttps://in.code\n```"))43 for _, want := range []string{44 `<strong><a href="https://012.run/wiki/agents">the page</a></strong>.<br>see <a href="https://example.com/x" rel="noopener">https://example.com/x</a>, and <a href="/open?target=nostr%3Anpub1ttrypewl3au52wqux86r22yt506c077k3maj02a0jste97wrvd5sjfutc2">nostr:npub1ttrypewl3au52wqux86r22yt506c077k3maj02a0jste97wrvd5sjfutc2</a></p>`,45 "<li><code>code https://not.a.link</code></li>",46 "<li>[x](javascript:alert(1))</li>",47 "<pre><code>https://in.code</code></pre>",48 } {49 if !strings.Contains(got, want) {50 t.Errorf("missing %q in %s", want, got)51 }52 }53 if strings.Count(got, "<a ") != 3 {54 t.Fatalf("expected three links, got %s", got)55 }56}5758func TestMarkdownRendersTablesWithAlignmentAndInlineMarkdown(t *testing.T) {59 source := "| Name | Count | Notes |\n| :--- | ---: | :---: |\n| **Ada** | 3 | [docs](https://example.com) |\n| Grace | | `a | b` |"60 got := string(renderMarkdown(source))61 for _, want := range []string{62 `<div data-markdown-table="" role="region" aria-label="Table" tabindex="0"><table>`,63 `<thead><tr><th scope="col" data-align="left">Name</th><th scope="col" data-align="right">Count</th><th scope="col" data-align="center">Notes</th></tr></thead>`,64 `<tbody><tr><td data-align="left"><strong>Ada</strong></td><td data-align="right">3</td><td data-align="center"><a href="https://example.com">docs</a></td></tr>`,65 `<tr><td data-align="left">Grace</td><td data-align="right"></td><td data-align="center"><code>a | b</code></td></tr></tbody></table></div>`,66 } {67 if !strings.Contains(got, want) {68 t.Errorf("missing %q in %s", want, got)69 }70 }71}7273func TestMarkdownTableEscapesPipesAndLeavesMalformedInputAsText(t *testing.T) {74 got := string(renderMarkdown("a \\| b\n---\n\n| one | two |\n| --- |\n| three | four | five |"))75 if strings.Contains(got, `data-markdown-table`) {76 t.Fatalf("malformed table was rendered: %s", got)77 }78 if !strings.Contains(got, `a \| b ---`) || !strings.Contains(got, `| one | two |`) {79 t.Fatalf("malformed table was not preserved as text: %s", got)80 }8182 got = string(renderMarkdown("a \\| b | c\n--- | ---\nvalue \\| kept | ok"))83 if !strings.Contains(got, `<th scope="col">a | b</th>`) || !strings.Contains(got, `<td>value | kept</td>`) {84 t.Fatalf("escaped pipe was split or escaped incorrectly: %s", got)85 }86}8788func TestChatMarkdownAutolinksTableText(t *testing.T) {89 got := string(renderChatMarkdown("| Link |\n| --- |\n| https://example.com/path |"))90 if !strings.Contains(got, `<td><a href="https://example.com/path" rel="noopener">https://example.com/path</a></td>`) {91 t.Fatalf("table cell was not autolinked: %s", got)92 }93}9495func TestMarkdownTableStopsBeforeHeadingsAndFences(t *testing.T) {96 got := string(renderMarkdown("| Name | Value |\n| --- | --- |\n| one | two |\n# | Heading |\n\n```sh | example\n| code |\n```"))97 if strings.Contains(got, "<td># | Heading |</td>") || strings.Contains(got, "<td>```sh | example</td>") {98 t.Fatalf("table consumed a following block: %s", got)99 }100 if !strings.Contains(got, "<h1>| Heading |</h1>") || !strings.Contains(got, "<pre><code data-lang=\"sh\">| code |</code></pre>") {101 t.Fatalf("following heading or fence was not rendered separately: %s", got)102 }103}