// // test-MarkdownParser.cc // markdownparser // // Created by Zdenek Nemec on 4/18/14. // Copyright (c) 2014 Apiary Inc. All rights reserved. // #include "catch.hpp" #include "MarkdownParser.h" using namespace mdp; TEST_CASE("Parse one paragaraph", "[parser][paragraph]") { MarkdownParser parser; MarkdownNode ast; // NOTE: +1 Error // Used version of sundown automatically adds a newline if one is missing. // If the input buffer does not ends with new line it might be "prolonged". parser.parse("Hello World!\n", ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 13); MarkdownNode& node = ast.children().front(); REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "Hello World!"); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 13); } TEST_CASE("Parse when starting with empty line", "[parser][empty_line]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src =\ "\n"\ "Lorem\n"\ "\n"\ "Ipsum\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 2); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 14); MarkdownNode& node = ast.children()[0]; REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "Lorem"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 1); REQUIRE(node.sourceMap[0].length == 7); node = ast.children()[1]; REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "Ipsum"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 8); REQUIRE(node.sourceMap[0].length == 6); } TEST_CASE("Parse multiple paragaraphs", "[parser][paragraph]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src =\ "Lorem\n"\ "\n"\ "Ipsum\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 2); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 13); MarkdownNode& node = ast.children()[0]; REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "Lorem"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 7); node = ast.children()[1]; REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "Ipsum"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 7); REQUIRE(node.sourceMap[0].length == 6); } TEST_CASE("Parse header", "[parser][header]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "# Header\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 9); MarkdownNode& node = ast.children().front(); REQUIRE(node.type == HeaderMarkdownNodeType); REQUIRE(node.text == "Header"); REQUIRE(node.data == 1); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 9); } TEST_CASE("Parse multiple headers", "[parser][header]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src =\ "# Header 1\n"\ "## Header 2\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 2); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 23); MarkdownNode node = ast.children()[0]; REQUIRE(node.type == HeaderMarkdownNodeType); REQUIRE(node.text == "Header 1"); REQUIRE(node.data == 1); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 11); node = ast.children()[1]; REQUIRE(node.type == HeaderMarkdownNodeType); REQUIRE(node.text == "Header 2"); REQUIRE(node.data == 2); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 11); REQUIRE(node.sourceMap[0].length == 12); } TEST_CASE("Parse horizontal rule", "[parser][hrule]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "---\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 4); MarkdownNode node = ast.children().front(); REQUIRE(node.type == HRuleMarkdownNodeType); REQUIRE(node.text.empty()); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 4); } TEST_CASE("Parse code block", "[parser][code]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = " 42\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 20); MarkdownNode node = ast.children().front(); REQUIRE(node.type == CodeMarkdownNodeType); REQUIRE(node.text == "42\n"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 20); } TEST_CASE("Parse HTML block tag", "[parser][html]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "
some
\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 16); MarkdownNode node = ast.children().front(); REQUIRE(node.type == HTMLMarkdownNodeType); REQUIRE(node.text == "
some
\n"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 16); } TEST_CASE("Parse single list item", "[parser][list]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "- list item\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 12); MarkdownNode node = ast.children()[0]; REQUIRE(node.type == ListItemMarkdownNodeType); REQUIRE(node.text.empty()); REQUIRE(node.data == 0); REQUIRE(node.children().size() == 1); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 12); node = node.children()[0]; REQUIRE(node.type == ParagraphMarkdownNodeType); REQUIRE(node.text == "list item\n"); REQUIRE(node.data == 0); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 2); REQUIRE(node.sourceMap[0].length == 10); } TEST_CASE("Parse nested list items", "[parser][list]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src =\ "- A\n"\ " - B\n"\ " - C\n"\ " - D\n"\ "- E\n"; /* Topology: + root + list item + paragraph "A" + list item + paragraph "B" + list item + paragraph "C" + list item + paragraph "D" + list item + paragraph "E" */ parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 2); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 36); // List Item A MarkdownNode& itemA = ast.children()[0]; REQUIRE(itemA.type == ListItemMarkdownNodeType); REQUIRE(itemA.text.empty()); REQUIRE(itemA.data == 0); REQUIRE(itemA.children().size() == 3); REQUIRE(itemA.sourceMap.size() == 1); REQUIRE(itemA.sourceMap[0].location == 0); REQUIRE(itemA.sourceMap[0].length == 32); REQUIRE(itemA.children()[0].type == ParagraphMarkdownNodeType); REQUIRE(itemA.children()[0].text == "A\n"); REQUIRE(itemA.children()[0].data == 0); REQUIRE(itemA.children()[0].children().empty()); REQUIRE(itemA.children()[0].sourceMap.size() == 1); REQUIRE(itemA.children()[0].sourceMap[0].location == 2); REQUIRE(itemA.children()[0].sourceMap[0].length == 2); // List Item B MarkdownNode itemB = itemA.children()[1]; REQUIRE(itemB.type == ListItemMarkdownNodeType); REQUIRE(itemB.text.empty()); REQUIRE(itemB.data == 0); REQUIRE(itemB.children().size() == 2); REQUIRE(itemB.sourceMap.size() == 2); REQUIRE(itemB.sourceMap[0].location == 8); REQUIRE(itemB.sourceMap[0].length == 4); REQUIRE(itemB.sourceMap[1].location == 16); REQUIRE(itemB.sourceMap[1].length == 8); REQUIRE(itemB.children()[0].type == ParagraphMarkdownNodeType); REQUIRE(itemB.children()[0].text == "B\n"); REQUIRE(itemB.children()[0].data == 0); REQUIRE(itemB.children()[0].children().empty()); MarkdownNode paraBX = itemB.children()[0]; REQUIRE(itemB.children()[0].sourceMap.size() == 1); REQUIRE(itemB.children()[0].sourceMap[0].location == 10); REQUIRE(itemB.children()[0].sourceMap[0].length == 2); // List Item C MarkdownNode itemC = itemB.children()[1]; REQUIRE(itemC.type == ListItemMarkdownNodeType); REQUIRE(itemC.text.empty()); REQUIRE(itemC.data == 0); REQUIRE(itemC.children().size() == 1); REQUIRE(itemC.sourceMap.size() == 1); REQUIRE(itemC.sourceMap[0].location == 20); REQUIRE(itemC.sourceMap[0].length == 4); REQUIRE(itemC.children()[0].type == ParagraphMarkdownNodeType); REQUIRE(itemC.children()[0].text == "C\n"); REQUIRE(itemC.children()[0].data == 0); REQUIRE(itemC.children()[0].children().empty()); REQUIRE(itemC.children()[0].sourceMap.size() == 1); REQUIRE(itemC.children()[0].sourceMap[0].location == 22); REQUIRE(itemC.children()[0].sourceMap[0].length == 2); // List Item D MarkdownNode itemD = itemA.children()[2]; REQUIRE(itemD.type == ListItemMarkdownNodeType); REQUIRE(itemD.text.empty()); REQUIRE(itemD.data == 0); REQUIRE(itemD.children().size() == 1); REQUIRE(itemD.sourceMap.size() == 1); REQUIRE(itemD.sourceMap[0].location == 28); REQUIRE(itemD.sourceMap[0].length == 4); REQUIRE(itemD.children()[0].type == ParagraphMarkdownNodeType); REQUIRE(itemD.children()[0].text == "D\n"); REQUIRE(itemD.children()[0].data == 0); REQUIRE(itemD.children()[0].children().empty()); REQUIRE(itemD.children()[0].sourceMap.size() == 1); REQUIRE(itemD.children()[0].sourceMap[0].location == 30); REQUIRE(itemD.children()[0].sourceMap[0].length == 2); // List Item E MarkdownNode itemE = ast.children()[1]; REQUIRE(itemE.type == ListItemMarkdownNodeType); REQUIRE(itemE.text.empty()); REQUIRE(itemE.data == 0); REQUIRE(itemE.children().size() == 1); REQUIRE(itemE.sourceMap.size() == 1); REQUIRE(itemE.sourceMap[0].location == 32); REQUIRE(itemE.sourceMap[0].length == 4); REQUIRE(itemE.children()[0].type == ParagraphMarkdownNodeType); REQUIRE(itemE.children()[0].text == "E\n"); REQUIRE(itemE.children()[0].data == 0); REQUIRE(itemE.children()[0].children().empty()); REQUIRE(itemE.children()[0].sourceMap.size() == 1); REQUIRE(itemE.children()[0].sourceMap[0].location == 34); REQUIRE(itemE.children()[0].sourceMap[0].length == 2); } TEST_CASE("Parse list item with multiple paragraphs", "[parser][list]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src =\ "- A\n"\ "\n"\ " C\n"\ " D\n"\ "\n"\ " E\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 24); MarkdownNode node = ast.children()[0]; REQUIRE(node.type == ListItemMarkdownNodeType); REQUIRE(node.text.empty()); REQUIRE(node.data == 2); REQUIRE(node.children().size() == 3); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 24); MarkdownNode& p1 = node.children()[0]; REQUIRE(p1.type == ParagraphMarkdownNodeType); REQUIRE(p1.text == "A"); REQUIRE(p1.data == 0); REQUIRE(p1.children().empty()); REQUIRE(p1.sourceMap.size() == 1); REQUIRE(p1.sourceMap[0].location == 2); REQUIRE(p1.sourceMap[0].length == 3); MarkdownNode& p2 = node.children()[1]; REQUIRE(p2.type == ParagraphMarkdownNodeType); REQUIRE(p2.text == "C\nD"); REQUIRE(p2.data == 0); REQUIRE(p2.children().empty()); REQUIRE(p2.sourceMap.size() == 2); REQUIRE(p2.sourceMap[0].location == 9); REQUIRE(p2.sourceMap[0].length == 2); REQUIRE(p2.sourceMap[1].location == 15); REQUIRE(p2.sourceMap[1].length == 3); MarkdownNode& p3 = node.children()[2]; REQUIRE(p3.type == ParagraphMarkdownNodeType); REQUIRE(p3.text == "E"); REQUIRE(p3.data == 0); REQUIRE(p3.children().empty()); REQUIRE(p3.sourceMap.size() == 1); REQUIRE(p3.sourceMap[0].location == 22); REQUIRE(p3.sourceMap[0].length == 2); } TEST_CASE("Parse a simple quote", "[parser][quote]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "> quote\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 8); MarkdownNode& quote = ast.children()[0]; REQUIRE(quote.type == QuoteMarkdownNodeType); REQUIRE(quote.text.empty()); REQUIRE(quote.data == 0); REQUIRE(quote.children().size() == 1); REQUIRE(quote.sourceMap.size() == 1); REQUIRE(quote.sourceMap[0].location == 0); REQUIRE(quote.sourceMap[0].length == 8); MarkdownNode& para = quote.children()[0]; REQUIRE(para.type == ParagraphMarkdownNodeType); REQUIRE(para.text == "quote"); REQUIRE(para.data == 0); REQUIRE(para.children().empty()); REQUIRE(para.sourceMap.size() == 1); REQUIRE(para.sourceMap[0].location == 2); REQUIRE(para.sourceMap[0].length == 6); } TEST_CASE("Source map crash", "[parser][sourcemap][issue][snowcrash][62]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = \ "* B\n"\ ">* CCC CC\n"\ ">* D\n"\ "\n"\ "* E\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 2); } TEST_CASE("Map node without trailing newline", "[parser][sourcemap]") { MarkdownParser parser; MarkdownNode ast; ByteBuffer src = "# Hello World"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 13); MarkdownNode& node = ast.children().front(); REQUIRE(node.type == HeaderMarkdownNodeType); REQUIRE(node.text == "Hello World"); REQUIRE(node.children().empty()); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 13); } TEST_CASE("Multi-paragraph list item source map", "[parser][sourcemap]") { MarkdownParser parser; MarkdownNode ast; // List item with two paragraphs & lazy indentation: // //+ Lorem ipsum //dolor sit amet // // consectetur //adipiscing elit ByteBuffer src = \ "+ Lorem ipsum\n"\ "dolor sit amet\n"\ "\n"\ " consectetur\n"\ "adipiscing elit\n"; parser.parse(src, ast); REQUIRE(ast.type == RootMarkdownNodeType); REQUIRE(ast.text.empty()); REQUIRE(ast.data == 0); REQUIRE(ast.children().size() == 1); REQUIRE(ast.sourceMap.size() == 1); REQUIRE(ast.sourceMap[0].location == 0); REQUIRE(ast.sourceMap[0].length == 64); MarkdownNode& node = ast.children().front(); REQUIRE(node.type == ListItemMarkdownNodeType); REQUIRE(node.children().size() == 2); REQUIRE(node.sourceMap.size() == 1); REQUIRE(node.sourceMap[0].location == 0); REQUIRE(node.sourceMap[0].length == 64); MarkdownNode& p1 = node.children().front(); REQUIRE(p1.text == " Lorem ipsum\ndolor sit amet"); REQUIRE(p1.children().empty()); REQUIRE(p1.sourceMap.size() == 1); REQUIRE(p1.sourceMap[0].location == 2); REQUIRE(p1.sourceMap[0].length == 30); MarkdownNode& p2 = node.children().back(); REQUIRE(p2.text == "consectetur\nadipiscing elit"); REQUIRE(p2.children().empty()); REQUIRE(p2.sourceMap.size() == 1); REQUIRE(p2.sourceMap[0].location == 36); REQUIRE(p2.sourceMap[0].length == 28); }