#include "htmlrenderer.h" #include #include #include "3rd-party/catch.hpp" #include "strprintf.h" #include "utils.h" using namespace newsboat; const std::string url = "http://example.com/feed.rss"; /* * We're going to do an awful lot of comparisons of these pairs, so it makes * sense to create a function to make constructing them easier. * * And while we're at it, let's make it accept char* instead of string so that * we don't have to wrap string literals in std::string() calls (we don't * support C++14 yet so can't use their fancy string literal operators.) */ std::pair p(newsboat::LineType type, const char* str) { return std::make_pair(type, std::string(str)); } namespace Catch { /* Catch doesn't know how to print out newsboat::LineType values, so * let's teach it! * * Technically, any one of the following two definitions shouls be enough, * but somehow it's not that way: toString works in std::pair<> while * StringMaker is required for simple things like REQUIRE(LineType::hr == * LineType::hr). Weird, but at least this works. */ std::string toString(newsboat::LineType const& value) { switch (value) { case LineType::wrappable: return "wrappable"; case LineType::softwrappable: return "softwrappable"; case LineType::nonwrappable: return "nonwrappable"; case LineType::hr: return "hr"; default: return "(unknown LineType)"; } } template<> struct StringMaker { static std::string convert(newsboat::LineType const& value) { return toString(value); } }; // Catch also doesn't know about std::pair template<> struct StringMaker> { static std::string convert( std::pair const& value) { std::ostringstream o; o << "("; o << toString(value.first); o << ", \""; o << value.second; o << "\")"; return o.str(); } }; } // namespace Catch TEST_CASE( "Links are rendered as underlined text with reference number in " "square brackets", "[HtmlRenderer]") { HtmlRenderer rnd; std::vector> lines; std::vector links; rnd.render("slashdot", lines, links, ""); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "slashdot[1]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://slashdot.org/ (link)")); REQUIRE(links[0].first == "http://slashdot.org/"); REQUIRE(links[0].second == LinkType::HREF); } TEST_CASE("
,
and
result in a line break", "[HtmlRenderer]") { HtmlRenderer rnd; std::vector> lines; std::vector links; for (std::string tag : { "
", "
", "
" }) { SECTION(tag) { auto input = "hello" + tag + "world!"; rnd.render(input, lines, links, ""); REQUIRE(lines.size() == 2); REQUIRE(lines[0] == p(LineType::wrappable, "hello")); REQUIRE(lines[1] == p(LineType::wrappable, "world!")); } } } TEST_CASE("Superscript is rendered with caret symbol", "[HtmlRenderer]") { HtmlRenderer rnd; std::vector> lines; std::vector links; rnd.render("310", lines, links, ""); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "3^10")); } TEST_CASE("Subscript is rendered with square brackets", "[HtmlRenderer]") { HtmlRenderer rnd; std::vector> lines; std::vector links; rnd.render("Ai", lines, links, ""); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "A[i]")); } TEST_CASE("Script tags are ignored", "[HtmlRenderer]") { HtmlRenderer rnd; std::vector> lines; std::vector links; rnd.render("abc", lines, links, ""); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "abc")); } TEST_CASE("format_ol_count formats list count in specified format", "[HtmlRenderer]") { HtmlRenderer r; SECTION("format to digit") { REQUIRE(r.format_ol_count(1, '1') == " 1"); REQUIRE(r.format_ol_count(3, '1') == " 3"); } SECTION("format to alphabetic") { REQUIRE(r.format_ol_count(3, 'a') == "c"); REQUIRE(r.format_ol_count(26 + 3, 'a') == "ac"); REQUIRE(r.format_ol_count(3 * 26 * 26 + 5 * 26 + 2, 'a') == "ceb"); } SECTION("format to alphabetic uppercase") { REQUIRE(r.format_ol_count(3, 'A') == "C"); REQUIRE(r.format_ol_count(26 + 5, 'A') == "AE"); REQUIRE(r.format_ol_count(27, 'A') == "AA"); REQUIRE(r.format_ol_count(26, 'A') == "Z"); REQUIRE(r.format_ol_count(26 * 26 + 26, 'A') == "ZZ"); REQUIRE(r.format_ol_count(25 * 26 * 26 + 26 * 26 + 26, 'A') == "YZZ"); } SECTION("format to roman numerals") { REQUIRE(r.format_ol_count(1, 'i') == "i"); REQUIRE(r.format_ol_count(2, 'i') == "ii"); REQUIRE(r.format_ol_count(5, 'i') == "v"); REQUIRE(r.format_ol_count(4, 'i') == "iv"); REQUIRE(r.format_ol_count(6, 'i') == "vi"); REQUIRE(r.format_ol_count(7, 'i') == "vii"); REQUIRE(r.format_ol_count(10, 'i') == "x"); REQUIRE(r.format_ol_count(32, 'i') == "xxxii"); REQUIRE(r.format_ol_count(1972, 'i') == "mcmlxxii"); } SECTION("format to uppercase roman numerals") { REQUIRE(r.format_ol_count(2011, 'I') == "MMXI"); } } TEST_CASE("links with same URL are coalesced under one number", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "About us" "Another link"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/about"); REQUIRE(links[0].second == LinkType::HREF); } TEST_CASE("links with different URLs have different numbers", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "One" "Two"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(links.size() == 2); REQUIRE(links[0].first == "http://example.com/one"); REQUIRE(links[0].second == LinkType::HREF); REQUIRE(links[1].first == "http://example.com/two"); REQUIRE(links[1].second == LinkType::HREF); } TEST_CASE("link without `href' is neither highlighted nor added to links list", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "test"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "test")); REQUIRE(links.size() == 0); } TEST_CASE( "link with empty `href' is neither highlighted nor added to links list", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "test"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "test")); REQUIRE(links.size() == 0); } TEST_CASE(" is rendered in bold font", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "test"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "test")); } TEST_CASE(" is rendered as underlined text", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "test"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "test")); } TEST_CASE(" is rendered as text in quotes", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "test"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "\"test\"")); } TEST_CASE("Flash s are added to links if `src' is set", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "" ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[embedded flash: 1]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/game.swf (embedded flash)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/game.swf"); REQUIRE(links[0].second == LinkType::EMBED); } TEST_CASE("Flash s are ignored if `src' is not set", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 0); REQUIRE(links.size() == 0); } TEST_CASE("non-flash s are ignored", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 0); REQUIRE(links.size() == 0); } TEST_CASE("s are ignored if `type' is not set", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 0); REQUIRE(links.size() == 0); } TEST_CASE(""); std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, "")); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[iframe 1 (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: https://www.youtube.com/embed/0123456789A (iframe)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "https://www.youtube.com/embed/0123456789A"); REQUIRE(links[0].second == LinkType::IFRAME); } TEST_CASE(""); std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, "")); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[iframe 1: My Video (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: https://www.youtube.com/embed/0123456789A (iframe)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "https://www.youtube.com/embed/0123456789A"); REQUIRE(links[0].second == LinkType::IFRAME); } TEST_CASE(""); std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, "")); REQUIRE(lines.size() == 0); REQUIRE(links.size() == 0); } TEST_CASE("spaces and line breaks are preserved inside
", "[HtmlRenderer]")
{
	HtmlRenderer r;

	const std::string input =
		"
oh cool\n"
		"\n"
		"  check this\tstuff  out!\n"
		"     \n"
		"neat huh?\n"
		"
"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 5); REQUIRE(lines[0] == p(LineType::softwrappable, "oh cool")); REQUIRE(lines[1] == p(LineType::softwrappable, "")); REQUIRE(lines[2] == p(LineType::softwrappable, " check this\tstuff out!")); REQUIRE(lines[3] == p(LineType::softwrappable, " ")); REQUIRE(lines[4] == p(LineType::softwrappable, "neat huh?")); REQUIRE(links.size() == 0); } TEST_CASE("tags still work inside
", "[HtmlRenderer]")
{
	HtmlRenderer r;

	const std::string input =
		"
"
		"bold text"
		"underlined text"
		"
"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::softwrappable, "bold textunderlined text")); REQUIRE(links.size() == 0); } TEST_CASE("line breaks are preserved in tags inside
", "[HtmlRenderer]")
{
	HtmlRenderer r;

	const std::string input =
		"
\n\n\n"
		"very coolvery cool indeed\n\n\n"
		"
"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::softwrappable, "")); REQUIRE(lines[1] == p(LineType::softwrappable, "")); REQUIRE(lines[2] == p(LineType::softwrappable, "very coolvery cool indeed")); REQUIRE(lines[3] == p(LineType::softwrappable, "")); REQUIRE(links.size() == 0); } TEST_CASE(" results in a placeholder and a link", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1 (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/image.png (image)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/image.png"); REQUIRE(links[0].second == LinkType::IMG); } TEST_CASE( " results in a placeholder with the correct index, " "and a link", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "My Page" " and an image: " ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 5); REQUIRE(lines[0] == p(LineType::wrappable, "My Page[1] and an image: [image 1 (link #2)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/index.html (link)")); REQUIRE(lines[4] == p(LineType::softwrappable, "[2]: http://example.com/image.png (image)")); REQUIRE(links.size() == 2); REQUIRE(links[0].first == "http://example.com/index.html"); REQUIRE(links[0].second == LinkType::HREF); REQUIRE(links[1].first == "http://example.com/image.png"); REQUIRE(links[1].second == LinkType::IMG); } TEST_CASE( "s without `src', or with empty `src', are ignored", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "" "" ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1 (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/image.png (image)")); REQUIRE(links.size() == 1); } TEST_CASE("alt is mentioned in placeholder if has `alt'", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1: Just a test image (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/image.png (image)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/image.png"); REQUIRE(links[0].second == LinkType::IMG); } TEST_CASE("alt is mentioned in placeholder if has `alt' and `title", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1: Just a test image (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/image.png (image)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/image.png"); REQUIRE(links[0].second == LinkType::IMG); } TEST_CASE( "title is mentioned in placeholder if has `title' but not `alt'", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = ""; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1: Just a test image (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: http://example.com/image.png (image)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "http://example.com/image.png"); REQUIRE(links[0].second == LinkType::IMG); } TEST_CASE( "URL of with data inside `src' is replaced with string " "\"inline image\"", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "Red dot"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[0] == p(LineType::wrappable, "[image 1: Red dot (link #1)]")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, "Links: ")); REQUIRE(lines[3] == p(LineType::softwrappable, "[1]: inline image (image)")); REQUIRE(links.size() == 1); REQUIRE(links[0].first == "inline image"); REQUIRE(links[0].second == LinkType::IMG); } TEST_CASE("
is indented and is separated by empty lines", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "
" "Experience is what you get when you didn't get what you " "wanted. " "—Randy Pausch" "
"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[0] == p(LineType::wrappable, "")); REQUIRE(lines[1] == p(LineType::wrappable, " Experience is what you get when you didn't get " "what you wanted. —Randy Pausch")); REQUIRE(lines[2] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } TEST_CASE( "
,
and
are rendered as a set of paragraphs with term " "descriptions indented to the right", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = // Opinions are lifted from the "Monstrous Regiment" by Terry // Pratchett "
" "
Coffee
" "
Foul muck
" "
Tea
" "
Soldier's friend
" "
"; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 8); REQUIRE(lines[0] == p(LineType::wrappable, "Coffee")); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(lines[2] == p(LineType::wrappable, " Foul muck")); REQUIRE(lines[3] == p(LineType::wrappable, "")); REQUIRE(lines[4] == p(LineType::wrappable, "Tea")); REQUIRE(lines[5] == p(LineType::wrappable, "")); REQUIRE(lines[6] == p(LineType::wrappable, " Soldier's friend")); REQUIRE(lines[7] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } TEST_CASE(" and

", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; SECTION("

is rendered with setext-style underlining") { const std::string input = "

Why are we here?

"; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 2); REQUIRE(lines[0] == p(LineType::wrappable, "Why are we here?")); REQUIRE(lines[1] == p(LineType::wrappable, "----------------")); REQUIRE(links.size() == 0); } for (auto tag : { "

", "

", "

", "

", "
", "

" }) { DYNAMIC_SECTION("When alone, " << tag << " generates only one line") { std::string closing_tag = tag; closing_tag.insert(1, "/"); const std::string input = tag + std::string("hello world") + closing_tag; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "hello world")); REQUIRE(links.size() == 0); } } SECTION("There's always an empty line between header/paragraph/list " "and paragraph") { SECTION("

") { const std::string input = "

header

paragraph

"; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[2] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } for (auto tag : { "

", "

", "

", "

", "
", "

" }) { SECTION(tag) { std::string closing_tag = tag; closing_tag.insert(1, "/"); const std::string input = tag + std::string("header") + closing_tag + "

paragraph

"; REQUIRE_NOTHROW( r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[1] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } } SECTION("
    ") { const std::string input = "
    • one
    • two
    • paragraph

      "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 5); REQUIRE(lines[3] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } SECTION("
        ") { const std::string input = "
        1. one
        2. two
        3. paragraph

          "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 5); REQUIRE(lines[3] == p(LineType::wrappable, "")); REQUIRE(links.size() == 0); } } } TEST_CASE("whitespace is erased at the beginning of the paragraph", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "

          " " \n" "here comes the text" "

          "; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "here comes the text")); REQUIRE(links.size() == 0); } TEST_CASE("newlines are replaced with space", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "newlines\nshould\nbe\nreplaced\nwith\na\nspace\ncharacter."; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "newlines should be replaced with a space character.")); REQUIRE(links.size() == 0); } TEST_CASE("paragraph is just a long line of text", "[HtmlRenderer]") { HtmlRenderer r; const std::string input = "

          " "here comes a long, boring chunk text that we have to fit to " "width" "

          "; std::vector> lines; std::vector links; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::wrappable, "here comes a long, boring chunk text " "that we have to fit to width")); REQUIRE(links.size() == 0); } TEST_CASE("default style for
            is Arabic numerals", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; SECTION("no `type' attribute") { const std::string input = "
              " "
            1. one
            2. " "
            3. two
            4. " "
            "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, " 1. one")); REQUIRE(lines[2] == p(LineType::wrappable, " 2. two")); REQUIRE(links.size() == 0); } SECTION("invalid `type' attribute") { const std::string input = "
              " "
            1. one
            2. " "
            3. two
            4. " "
            "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, " 1. one")); REQUIRE(lines[2] == p(LineType::wrappable, " 2. two")); REQUIRE(links.size() == 0); } } TEST_CASE("default starting number for
              is 1", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; SECTION("no `start' attribute") { const std::string input = "
                " "
              1. one
              2. " "
              3. two
              4. " "
              "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, " 1. one")); REQUIRE(lines[2] == p(LineType::wrappable, " 2. two")); REQUIRE(links.size() == 0); } SECTION("invalid `start' attribute") { const std::string input = "
                " "
              1. one
              2. " "
              3. two
              4. " "
              "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, " 1. one")); REQUIRE(lines[2] == p(LineType::wrappable, " 2. two")); REQUIRE(links.size() == 0); } } TEST_CASE("type='1' for
                means Arabic numbering", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                  " "
                1. one
                2. " "
                3. two
                4. " "
                "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, " 1. one")); REQUIRE(lines[2] == p(LineType::wrappable, " 2. two")); REQUIRE(links.size() == 0); } TEST_CASE("type='a' for
                  means lowercase alphabetic numbering", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                    " "
                  1. one
                  2. " "
                  3. two
                  4. " "
                  "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, "a. one")); REQUIRE(lines[2] == p(LineType::wrappable, "b. two")); REQUIRE(links.size() == 0); } TEST_CASE("type='A' for
                    means uppercase alphabetic numbering", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                      " "
                    1. one
                    2. " "
                    3. two
                    4. " "
                    "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, "A. one")); REQUIRE(lines[2] == p(LineType::wrappable, "B. two")); REQUIRE(links.size() == 0); } TEST_CASE("type='i' for
                      means lowercase Roman numbering", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                        " "
                      1. one
                      2. " "
                      3. two
                      4. " "
                      "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, "i. one")); REQUIRE(lines[2] == p(LineType::wrappable, "ii. two")); REQUIRE(links.size() == 0); } TEST_CASE("type='I' for
                        means uppercase Roman numbering", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                          " "
                        1. one
                        2. " "
                        3. two
                        4. " "
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 4); REQUIRE(lines[1] == p(LineType::wrappable, "I. one")); REQUIRE(lines[2] == p(LineType::wrappable, "II. two")); REQUIRE(links.size() == 0); } TEST_CASE("every next
                      1. implicitly closes the previous one", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                          " "
                        1. one" "
                        2. two
                        3. " "
                        4. three
                        5. " "" "
                        6. four
                        7. " "
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 6); REQUIRE(lines[1] == p(LineType::wrappable, "I. one")); REQUIRE(lines[2] == p(LineType::wrappable, "II. two")); REQUIRE(lines[3] == p(LineType::wrappable, "III. three")); REQUIRE(lines[4] == p(LineType::wrappable, "IV. four")); REQUIRE(links.size() == 0); } TEST_CASE(""; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 0); REQUIRE(links.size() == 0); } TEST_CASE("
                        is not a string, but a special type of line", "[HtmlRenderer]") { std::vector> lines; std::vector links; HtmlRenderer r; const std::string input = "
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(links.size() == 0); REQUIRE(lines.size() == 1); REQUIRE(lines[0].first == LineType::hr); } TEST_CASE("header rows of tables are in bold", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; SECTION("one column") { const std::string input = "" "" "" "" "
                        header
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::nonwrappable, "header")); REQUIRE(links.size() == 0); } SECTION("two columns") { const std::string input = "" "" "" "" "" "
                        anotherheader
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::nonwrappable, "another header")); REQUIRE(links.size() == 0); } } TEST_CASE("cells are separated by space if `border' is not set", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "" "" "" "" "
                        helloworld
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 1); REQUIRE(lines[0] == p(LineType::nonwrappable, "hello world")); REQUIRE(links.size() == 0); } TEST_CASE( "cells are separated by vertical bar if `border' is set (regardless " "of actual value)", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; for (auto border_width = 1; border_width < 10; ++border_width) { DYNAMIC_SECTION("`border' = " << border_width) { const std::string input_template = "" "" "" "" "" "
                        helloworld
                        "; const std::string input = strprintf::fmt(input_template, border_width); REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello|world|")); REQUIRE(links.size() == 0); } } } TEST_CASE("tables with `border' have borders", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "" "" "" "" "
                        helloworld
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[0] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello|world|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(links.size() == 0); } TEST_CASE("if document ends before is found, table is rendered anyway", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "" "" "" ""; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[0] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello|world|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(links.size() == 0); } TEST_CASE("tables can be nested", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                        helloworld
                        " "" "" "" "" "
                        " "" "" "" "" "" "" "" "" "" "
                        helloworld
                        anotherrow
                        " "
                        lonely cell
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 7); REQUIRE(lines[0] == p(LineType::nonwrappable, "+---------------+-----------+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|+-------+-----+|lonely cell|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "||hello |world|| |")); REQUIRE(lines[3] == p(LineType::nonwrappable, "|+-------+-----+| |")); REQUIRE(lines[4] == p(LineType::nonwrappable, "||another|row || |")); REQUIRE(lines[5] == p(LineType::nonwrappable, "|+-------+-----+| |")); REQUIRE(lines[6] == p(LineType::nonwrappable, "+---------------+-----------+")); REQUIRE(links.size() == 0); } TEST_CASE( "if appears inside table but outside of a row, one is created " "implicitly", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "" "" "" "
                        helloworld
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[0] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello|world|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "+-----+-----+")); REQUIRE(links.size() == 0); } TEST_CASE("previous row is implicitly closed when is found", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "" "" "
                        hello
                        world
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 5); REQUIRE(lines[0] == p(LineType::nonwrappable, "+-----+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "+-----+")); REQUIRE(lines[3] == p(LineType::nonwrappable, "|world|")); REQUIRE(lines[4] == p(LineType::nonwrappable, "+-----+")); REQUIRE(links.size() == 0); } TEST_CASE( "free-standing text outside of is implicitly concatenated with " "the next cell", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "hello" "" "" "
                        world
                        "; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 3); REQUIRE(lines[0] == p(LineType::nonwrappable, "+-----------+")); REQUIRE(lines[1] == p(LineType::nonwrappable, "|hello world|")); REQUIRE(lines[2] == p(LineType::nonwrappable, "+-----------+")); REQUIRE(links.size() == 0); } TEST_CASE("text within is to be treated specially", "[HtmlRenderer]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "" "hello world!\n" "I'm a description from an iTunes feed. " "Apple just puts plain text into <summary> tag." ""; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 2); REQUIRE(lines[0] == p(LineType::wrappable, "hello world!")); REQUIRE(lines[1] == p(LineType::wrappable, "I'm a description from an iTunes feed. Apple just " "puts plain text into <>summary> tag.")); REQUIRE(links.size() == 0); } TEST_CASE("When rendeing text, HtmlRenderer strips leading whitespace", "[HtmlRenderer][issue204]") { HtmlRenderer r; std::vector> lines; std::vector links; const std::string input = "
                        \n" " \n" // tabs " \n" // spaces " \n" // tabs " \n" // spaces " \n" // tabs " \n" // tabs " Text preceded by whitespace."; REQUIRE_NOTHROW(r.render(input, lines, links, url)); REQUIRE(lines.size() == 2); REQUIRE(lines[0] == p(LineType::wrappable, "")); REQUIRE(lines[1] == p(LineType::wrappable, "Text preceded by whitespace.")); REQUIRE(links.size() == 0); } TEST_CASE("