diff options
Diffstat (limited to 'tests/extensions/test-about-render.lua')
| -rw-r--r-- | tests/extensions/test-about-render.lua | 248 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 248 insertions, 0 deletions
diff --git a/tests/extensions/test-about-render.lua b/tests/extensions/test-about-render.lua new file mode 100644 index 0000000..aa7e6ad --- /dev/null +++ b/tests/extensions/test-about-render.lua @@ -0,0 +1,248 @@ +-- Unit checks for custom/extensions/about-render.lua, run under a standalone +-- Lua by t0501-about-render.sh with the script path as the first argument. +-- The second argument may be nolpeg, which makes the lpeg module fail to +-- load so the escaped plain text fallback is proven even on a machine where +-- lpeg is installed. Without it the markdown and man checks run when lpeg is +-- present and are skipped with a note when it is not, the same way the +-- filter itself degrades. + +local test_directory = arg[0]:match("^(.*)/") or "." +local h = dofile(test_directory .. "/harness.lua") + +local script = arg[1] +local mode = arg[2] or "" + +local has_lpeg = false +if mode == "nolpeg" then + h.preload_failure("lpeg") +else + has_lpeg = pcall(require, "lpeg") + if not has_lpeg then + io.write("lpeg not installed, markdown and man checks skipped\n") + end +end + +h.load(script) + +local function render(filename, text) + return h.run({ filename }, { text }) +end + +-- Dispatch and the plain text rendering need nothing but the script itself, +-- so they run whatever became of lpeg. + +local out, ret = render("README", "plain <text> & more") +h.contains("no extension renders as plain text", out, + "<pre class='plaintext'>plain <text> & more</pre>") +h.equals("close answers zero", ret, 0) + +out = render("notes.txt", "just text") +h.contains("an unknown extension renders as plain text", out, + "<pre class='plaintext'>just text</pre>") + +out = render(nil, "no name at all") +h.contains("a missing filename renders as plain text", out, + "<pre class='plaintext'>no name at all</pre>") + +-- cgit's C sink stops at the first NUL byte, which the script documents, so +-- the text past one is lost while the wrapper written separately survives. +out = render("README", "before\0after") +h.contains("text before a nul byte survives", out, "before") +h.excludes("text after a nul byte is dropped", out, "after") +h.contains("the wrapper written after the text survives", out, "</pre>") + +out = h.run({ "README" }, { "two ", "writes" }) +h.contains("writes are joined before rendering", out, "two writes") + +if mode == "nolpeg" then + out = render("README.md", "# Title") + h.contains("markdown without lpeg falls back to plain text", out, + "<pre class='plaintext'># Title</pre>") + out = render("page.1", ".SH NAME") + h.contains("man without lpeg falls back to plain text", out, + "<pre class='plaintext'>.SH NAME</pre>") + h.finish() +end + +if not has_lpeg then + h.finish() +end + +-- Markdown. + +out = render("README.md", "# Title") +h.contains("a heading renders inside the wrapper", + out, "<div class='markdown'><h1>Title</h1></div>") + +out = render("README.md", "## Sub ##") +h.contains("trailing hashes are stripped from a heading", out, + "<h2>Sub</h2>") + +out = render("README.MD", "# Upper") +h.contains("the extension matches whatever its case", out, "<h1>Upper</h1>") + +out = render("readme.markdown", "# Long") +h.contains("the long markdown extension dispatches too", out, + "<h1>Long</h1>") + +out = render("README.md", "line one\nline two") +h.contains("a paragraph keeps its line break", out, + "<p>line one<br>line two</p>") + +out = render("README.md", "first para\r\nsecond line") +h.contains("crlf line endings are normalised", out, + "<p>first para<br>second line</p>") + +out = render("README.md", "a **bold** and *leaning* word") +h.contains("double stars embolden", out, "<strong>bold</strong>") +h.contains("single stars lean", out, "<em>leaning</em>") + +out = render("README.md", "an __up__ and _down_ word") +h.contains("double underscores embolden", out, "<strong>up</strong>") +h.contains("single underscores lean", out, "<em>down</em>") + +out = render("README.md", "run `x < y` here") +h.contains("inline code is escaped", out, "<code>x < y</code>") + +out = render("README.md", "raw <script>alert(1)</script> here") +h.contains("markup in text reaches the page escaped", out, + "<script>alert(1)</script>") +h.excludes("no live tag slips through", out, "<script>") + +out = render("README.md", "[docs](https://example.com/docs)") +h.contains("an https link renders as an anchor", out, + "<a href='https://example.com/docs'>docs</a>") + +out = render("README.md", "[docs](docs/page.html)") +h.contains("a relative link renders as an anchor", out, + "<a href='docs/page.html'>docs</a>") + +out = render("README.md", "[mail](mailto:a@example.com)") +h.contains("a mailto link renders as an anchor", out, + "<a href='mailto:a@example.com'>mail</a>") + +out = render("README.md", "[bad](javascript:alert(1))") +h.excludes("a javascript link renders no anchor", out, "<a ") +h.contains("its text still reaches the page", out, "bad") + +out = render("README.md", "[bad](java\nscript:alert(1))") +h.excludes("a split scheme is still caught", out, "<a ") + +out = render("README.md", "[bad](//evil.example/x)") +h.excludes("a scheme relative link renders no anchor", out, "<a ") + +out = render("README.md", "[q](https://example.com/a'b)") +h.contains("a quote in a link target is escaped", out, + "href='https://example.com/a'b'") + +out = render("README.md", "") +h.contains("an image renders with its alt text escaped", out, + "<img src='https://example.com/i.png' alt='a & b'>") + +out = render("README.md", "") +h.excludes("an unsafe image renders no tag", out, "<img") +h.contains("its alt text is kept as text", out, "![alt]") + +out = render("README.md", "```lua\nlocal x = 1 < 2\n```\nafter") +h.contains("a fenced block carries its language", out, + "<pre><code data-lang='lua'>local x = 1 < 2\n</code></pre>") +h.contains("text after the fence renders on", out, "<p>after</p>") + +out = render("README.md", "~~~\n**not bold**\n~~~") +h.contains("no inline parsing happens inside a fence", out, + "<code>**not bold**\n</code>") + +out = render("README.md", "---") +h.contains("a rule of dashes renders a break", out, "<hr>") + +out = render("README.md", "- - -") +h.contains("a spaced rule renders a break too", out, "<hr>") + +out = render("README.md", "> quoted words") +h.contains("a quote renders as a blockquote", out, + "<blockquote><p>quoted words</p></blockquote>") + +out = render("README.md", "> > deep") +h.contains("quotes nest", out, + "<blockquote><blockquote><p>deep</p></blockquote></blockquote>") + +-- A hostile readme of stacked markers has to hit the depth ceiling instead +-- of the interpreter's stack. +out = render("README.md", string.rep("> ", 30) .. "x") +local _, quote_count = out:gsub("<blockquote>", "") +h.equals("quote nesting stops at the ceiling", quote_count, 24) +h.contains("the innermost text still renders", out, "x") + +out = render("README.md", string.rep("*a", 200)) +h.check("an emphasis bomb still renders something", #out > 0) + +out = render("README.md", "| a | b |\n| --- | --- |\n| 1 | 2 |") +h.contains("a pipe table renders its head", + out, "<thead><tr><th>a</th><th>b</th></tr></thead>") +h.contains("a pipe table renders its body", + out, "<tbody><tr><td>1</td><td>2</td></tr></tbody>") + +out = render("README.md", "a | b in prose") +h.excludes("a pipe without a delimiter row stays prose", out, "<table") + +out = render("README.md", "- first\n- second") +h.contains("dashes render an unordered list", out, + "<ul><li>first</li><li>second</li></ul>") + +out = render("README.md", "1. first\n2. second") +h.contains("numbers render an ordered list", out, + "<ol><li>first</li><li>second</li></ol>") + +out = render("README.md", "- plain\n1. numbered") +h.contains("a change of marker splits the list", out, "</ul><ol>") + +out = render("README.md", "- a **bold** item") +h.contains("list items parse their text", out, + "<li>a <strong>bold</strong> item</li>") + +out = render("README.md", string.rep("a", 512 * 1024 + 1)) +h.contains("an oversized readme renders as plain text", out, + "<pre class='plaintext'>") + +-- Man pages. + +out = render("page.1", ".SH NAME") +h.contains("a section heading renders", out, + "<div class='markdown manpage'><h2>NAME</h2></div>") + +out = render("page.man", '.SH "TWO WORDS"') +h.contains("a quoted heading argument is unquoted", out, + "<h2>TWO WORDS</h2>") + +out = render("page.3", ".SS Details") +h.contains("a subsection renders one level down", out, "<h3>Details</h3>") + +out = render("page.1", ".B bold words\nplain after") +h.contains("the bold macro renders strong", out, + "<p><strong>bold words</strong><br>plain after</p>") + +out = render("page.1", "a \\fBbold\\fR c") +h.contains("font escapes carry across a run", out, + "<p>a <strong>bold</strong> c</p>") + +out = render("page.1", "x \\(em y \\- z") +h.contains("character escapes are translated", out, "<p>x - y - z</p>") + +out = render("page.1", ".nf\ncode <x>\n.fi\nafter") +h.contains("a no fill block renders as code", out, + "<pre><code>code <x>\n</code></pre>") +h.contains("filling resumes after it", out, "<p>after</p>") + +out = render("page.1", '.\\" a comment\nvisible') +h.excludes("a roff comment is dropped", out, "comment") +h.contains("the line after it renders", out, "visible") + +out = render("page.1", "first\n\nsecond") +h.contains("a blank line splits paragraphs", out, + "<p>first</p><p>second</p>") + +out = render("page.1", ".TH title 1\nbody") +h.excludes("the title macro renders nothing itself", out, "title 1") +h.contains("the body after it renders", out, "<p>body</p>") + +h.finish() |
