| use ParseOpt as PO; | |
| use super::*; | |
| #[test] | |
| fn test_parse_simple() { | |
| let buf = "**abcd** rest"; | |
| let (t, r) = parse_simple_pat(buf.as_bytes(), b"**", b"**", PO::None, MdTree::Strong).unwrap(); | |
| assert_eq!(t, MdTree::Strong("abcd")); | |
| assert_eq!(r, b" rest"); | |
| // Escaping should fail | |
| let buf = r"**abcd\** rest"; | |
| let res = parse_simple_pat(buf.as_bytes(), b"**", b"**", PO::None, MdTree::Strong); | |
| assert!(res.is_none()); | |
| } | |
| #[test] | |
| fn test_parse_comment() { | |
| let opt = PO::TrimNoEsc; | |
| let buf = "<!-- foobar! -->rest"; | |
| let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap(); | |
| assert_eq!(t, MdTree::Comment("foobar!")); | |
| assert_eq!(r, b"rest"); | |
| let buf = r"<!-- foobar! \-->rest"; | |
| let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap(); | |
| assert_eq!(t, MdTree::Comment(r"foobar! \")); | |
| assert_eq!(r, b"rest"); | |
| } | |
| #[test] | |
| fn test_parse_heading() { | |
| let buf1 = "# Top level\nrest"; | |
| let (t, r) = parse_heading(buf1.as_bytes()).unwrap(); | |
| assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Top level")].into())); | |
| assert_eq!(r, b"\nrest"); | |
| let buf1 = "# Empty"; | |
| let (t, r) = parse_heading(buf1.as_bytes()).unwrap(); | |
| assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Empty")].into())); | |
| assert_eq!(r, b""); | |
| // Combo | |
| let buf2 = "### Top `level` _woo_\nrest"; | |
| let (t, r) = parse_heading(buf2.as_bytes()).unwrap(); | |
| assert_eq!( | |
| t, | |
| MdTree::Heading( | |
| 3, | |
| vec![ | |
| MdTree::PlainText("Top "), | |
| MdTree::CodeInline("level"), | |
| MdTree::PlainText(" "), | |
| MdTree::Emphasis("woo"), | |
| ] | |
| .into() | |
| ) | |
| ); | |
| assert_eq!(r, b"\nrest"); | |
| } | |
| #[test] | |
| fn test_parse_code_inline() { | |
| let buf1 = "`abcd` rest"; | |
| let (t, r) = parse_codeinline(buf1.as_bytes()).unwrap(); | |
| assert_eq!(t, MdTree::CodeInline("abcd")); | |
| assert_eq!(r, b" rest"); | |
| // extra backticks, newline | |
| let buf2 = "```ab\ncd``` rest"; | |
| let (t, r) = parse_codeinline(buf2.as_bytes()).unwrap(); | |
| assert_eq!(t, MdTree::CodeInline("ab\ncd")); | |
| assert_eq!(r, b" rest"); | |
| // test no escaping | |
| let buf3 = r"`abcd\` rest"; | |
| let (t, r) = parse_codeinline(buf3.as_bytes()).unwrap(); | |
| assert_eq!(t, MdTree::CodeInline(r"abcd\")); | |
| assert_eq!(r, b" rest"); | |
| } | |
| #[test] | |
| fn test_parse_code_block() { | |
| let buf1 = "```rust\ncode\ncode\n```\nleftovers"; | |
| let (t, r) = parse_codeblock(buf1.as_bytes()); | |
| assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode", lang: Some("rust") }); | |
| assert_eq!(r, b"\nleftovers"); | |
| let buf2 = "`````\ncode\ncode````\n`````\nleftovers"; | |
| let (t, r) = parse_codeblock(buf2.as_bytes()); | |
| assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode````", lang: None }); | |
| assert_eq!(r, b"\nleftovers"); | |
| } | |
| #[test] | |
| fn test_parse_link() { | |
| let simple = "[see here](docs.rs) other"; | |
| let (t, r) = parse_any_link(simple.as_bytes(), false).unwrap(); | |
| assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" }); | |
| assert_eq!(r, b" other"); | |
| let simple_toplevel = "[see here](docs.rs) other"; | |
| let (t, r) = parse_any_link(simple_toplevel.as_bytes(), true).unwrap(); | |
| assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" }); | |
| assert_eq!(r, b" other"); | |
| let reference = "[see here] other"; | |
| let (t, r) = parse_any_link(reference.as_bytes(), true).unwrap(); | |
| assert_eq!(t, MdTree::RefLink { disp: "see here", id: None }); | |
| assert_eq!(r, b" other"); | |
| let reference_full = "[see here][docs-rs] other"; | |
| let (t, r) = parse_any_link(reference_full.as_bytes(), false).unwrap(); | |
| assert_eq!(t, MdTree::RefLink { disp: "see here", id: Some("docs-rs") }); | |
| assert_eq!(r, b" other"); | |
| let reference_def = "[see here]: docs.rs\nother"; | |
| let (t, r) = parse_any_link(reference_def.as_bytes(), true).unwrap(); | |
| assert_eq!(t, MdTree::LinkDef { id: "see here", link: "docs.rs" }); | |
| assert_eq!(r, b"\nother"); | |
| } | |
| const IND1: &str = r"test standard | |
| ind | |
| ind2 | |
| not ind"; | |
| const IND2: &str = r"test end of stream | |
| 1 | |
| 2 | |
| "; | |
| const IND3: &str = r"test empty lines | |
| 1 | |
| 2 | |
| not ind"; | |
| #[test] | |
| fn test_indented_section() { | |
| let (t, r) = get_indented_section(IND1.as_bytes()); | |
| assert_eq!(str::from_utf8(t).unwrap(), "test standard\n ind\n ind2"); | |
| assert_eq!(str::from_utf8(r).unwrap(), "\nnot ind"); | |
| let (txt, rest) = get_indented_section(IND2.as_bytes()); | |
| assert_eq!(str::from_utf8(txt).unwrap(), "test end of stream\n 1\n 2\n"); | |
| assert_eq!(str::from_utf8(rest).unwrap(), ""); | |
| let (txt, rest) = get_indented_section(IND3.as_bytes()); | |
| assert_eq!(str::from_utf8(txt).unwrap(), "test empty lines\n 1\n 2\n"); | |
| assert_eq!(str::from_utf8(rest).unwrap(), "\nnot ind"); | |
| } | |
| const HBT: &str = r"# Heading | |
| content"; | |
| #[test] | |
| fn test_heading_breaks() { | |
| let expected = vec![ | |
| MdTree::Heading(1, vec![MdTree::PlainText("Heading")].into()), | |
| MdTree::PlainText("content"), | |
| ] | |
| .into(); | |
| let res = entrypoint(HBT); | |
| assert_eq!(res, expected); | |
| } | |
| const NL1: &str = r"start | |
| end"; | |
| const NL2: &str = r"start | |
| end"; | |
| const NL3: &str = r"start | |
| end"; | |
| #[test] | |
| fn test_newline_breaks() { | |
| let expected = | |
| vec![MdTree::PlainText("start"), MdTree::ParagraphBreak, MdTree::PlainText("end")].into(); | |
| for (idx, check) in [NL1, NL2, NL3].iter().enumerate() { | |
| let res = entrypoint(check); | |
| assert_eq!(res, expected, "failed {idx}"); | |
| } | |
| } | |
| const WRAP: &str = "plain _italics | |
| italics_"; | |
| #[test] | |
| fn test_wrap_pattern() { | |
| let expected = vec![ | |
| MdTree::PlainText("plain "), | |
| MdTree::Emphasis("italics"), | |
| MdTree::Emphasis(" "), | |
| MdTree::Emphasis("italics"), | |
| ] | |
| .into(); | |
| let res = entrypoint(WRAP); | |
| assert_eq!(res, expected); | |
| } | |
| const WRAP_NOTXT: &str = r"_italics_ | |
| **bold**"; | |
| #[test] | |
| fn test_wrap_notxt() { | |
| let expected = | |
| vec![MdTree::Emphasis("italics"), MdTree::PlainText(" "), MdTree::Strong("bold")].into(); | |
| let res = entrypoint(WRAP_NOTXT); | |
| assert_eq!(res, expected); | |
| } | |
| const MIXED_LIST: &str = r"start | |
| - _italics item_ | |
| <!-- comment --> | |
| - **bold item** | |
| second line [link1](foobar1) | |
| third line [link2][link-foo] | |
| - :crab: | |
| extra indent | |
| end | |
| [link-foo]: foobar2 | |
| "; | |
| #[test] | |
| fn test_list() { | |
| let expected = vec![ | |
| MdTree::PlainText("start"), | |
| MdTree::ParagraphBreak, | |
| MdTree::UnorderedListItem(vec![MdTree::Emphasis("italics item")].into()), | |
| MdTree::LineBreak, | |
| MdTree::UnorderedListItem( | |
| vec![ | |
| MdTree::Strong("bold item"), | |
| MdTree::PlainText(" second line "), | |
| MdTree::Link { disp: "link1", link: "foobar1" }, | |
| MdTree::PlainText(" third line "), | |
| MdTree::Link { disp: "link2", link: "foobar2" }, | |
| ] | |
| .into(), | |
| ), | |
| MdTree::LineBreak, | |
| MdTree::UnorderedListItem( | |
| vec![MdTree::PlainText("🦀"), MdTree::PlainText(" extra indent")].into(), | |
| ), | |
| MdTree::ParagraphBreak, | |
| MdTree::PlainText("end"), | |
| ] | |
| .into(); | |
| let res = entrypoint(MIXED_LIST); | |
| assert_eq!(res, expected); | |
| } | |
| const SMOOSHED: &str = r#" | |
| start | |
| ### heading | |
| 1. ordered item | |
| ```rust | |
| println!("Hello, world!"); | |
| ``` | |
| `inline` | |
| ``end`` | |
| "#; | |
| #[test] | |
| fn test_without_breaks() { | |
| let expected = vec![ | |
| MdTree::PlainText("start"), | |
| MdTree::ParagraphBreak, | |
| MdTree::Heading(3, vec![MdTree::PlainText("heading")].into()), | |
| MdTree::OrderedListItem(1, vec![MdTree::PlainText("ordered item")].into()), | |
| MdTree::ParagraphBreak, | |
| MdTree::CodeBlock { txt: r#"println!("Hello, world!");"#, lang: Some("rust") }, | |
| MdTree::ParagraphBreak, | |
| MdTree::CodeInline("inline"), | |
| MdTree::PlainText(" "), | |
| MdTree::CodeInline("end"), | |
| ] | |
| .into(); | |
| let res = entrypoint(SMOOSHED); | |
| assert_eq!(res, expected); | |
| } | |
| const CODE_STARTLINE: &str = r#" | |
| start | |
| `code` | |
| middle | |
| `more code` | |
| end | |
| "#; | |
| #[test] | |
| fn test_code_at_start() { | |
| let expected = vec![ | |
| MdTree::PlainText("start"), | |
| MdTree::PlainText(" "), | |
| MdTree::CodeInline("code"), | |
| MdTree::PlainText(" "), | |
| MdTree::PlainText("middle"), | |
| MdTree::PlainText(" "), | |
| MdTree::CodeInline("more code"), | |
| MdTree::PlainText(" "), | |
| MdTree::PlainText("end"), | |
| ] | |
| .into(); | |
| let res = entrypoint(CODE_STARTLINE); | |
| assert_eq!(res, expected); | |
| } | |
| #[test] | |
| fn test_code_in_parens() { | |
| let expected = | |
| vec![MdTree::PlainText("("), MdTree::CodeInline("Foo"), MdTree::PlainText(")")].into(); | |
| let res = entrypoint("(`Foo`)"); | |
| assert_eq!(res, expected); | |
| } | |
| const LIST_WITH_SPACE: &str = " | |
| para | |
| * l1 | |
| * l2 | |
| "; | |
| #[test] | |
| fn test_list_with_space() { | |
| let expected = vec![ | |
| MdTree::PlainText("para"), | |
| MdTree::ParagraphBreak, | |
| MdTree::UnorderedListItem(vec![MdTree::PlainText("l1")].into()), | |
| MdTree::LineBreak, | |
| MdTree::UnorderedListItem(vec![MdTree::PlainText("l2")].into()), | |
| ] | |
| .into(); | |
| let res = entrypoint(LIST_WITH_SPACE); | |
| assert_eq!(res, expected); | |
| } | |
| const SNAKE_CASE: &str = " | |
| foo*bar* | |
| foo**bar** | |
| foo_bar_ | |
| foo__bar__ | |
| "; | |
| #[test] | |
| fn test_snake_case() { | |
| let expected = vec![ | |
| MdTree::PlainText("foo"), | |
| MdTree::Emphasis("bar"), | |
| MdTree::PlainText(" "), | |
| MdTree::PlainText("foo"), | |
| MdTree::Strong("bar"), | |
| MdTree::PlainText(" "), | |
| MdTree::PlainText("foo_bar_"), | |
| MdTree::PlainText(" "), | |
| MdTree::PlainText("foo__bar__"), | |
| ] | |
| .into(); | |
| let res = entrypoint(SNAKE_CASE); | |
| assert_eq!(res, expected); | |
| } | |
| #[test] | |
| fn test_codeblock_trailing_whitespace() { | |
| let buf = "```rust\ncode\n``` \nrest"; | |
| let (t, r) = parse_codeblock(buf.as_bytes()); | |
| assert_eq!(t, MdTree::CodeBlock { txt: "code", lang: Some("rust") }); | |
| assert_eq!(r, b"\nrest"); | |
| let buf = "```rust\ncode\n```abc\nrest"; | |
| let (t, r) = parse_codeblock(buf.as_bytes()); | |
| assert_eq!(t, MdTree::CodeBlock { txt: "code\n```abc\nrest", lang: Some("rust") }); | |
| assert_eq!(r, b""); | |
| } | |
| #[test] | |
| fn test_list_item_leading_whitespace() { | |
| // extra spaces after marker | |
| let buf = "- hello"; | |
| let (t, r) = parse_unordered_li(buf.as_bytes()); | |
| assert_eq!(t, MdTree::UnorderedListItem(vec![MdTree::PlainText("hello")].into())); | |
| assert_eq!(r, b""); | |
| // tab after the marker space | |
| let buf = "- \thello"; | |
| let (t, r) = parse_unordered_li(buf.as_bytes()); | |
| assert_eq!(t, MdTree::UnorderedListItem(vec![MdTree::PlainText("hello")].into())); | |
| assert_eq!(r, b""); | |
| // ordered list | |
| let buf = "1. hello"; | |
| let (t, r) = parse_ordered_li(buf.as_bytes()); | |
| assert_eq!(t, MdTree::OrderedListItem(1, vec![MdTree::PlainText("hello")].into())); | |
| assert_eq!(r, b""); | |
| } |