use super::helpers::{
assert_block_kinds, assert_block_kinds_for_node, find_all, find_first, parse_blocks,
parse_blocks_gfm, parse_blocks_quarto, parse_blocks_with_config,
};
use crate::options::{Extensions, Flavor, ParserOptions};
use crate::syntax::SyntaxKind;
fn get_code_content(node: &crate::syntax::SyntaxNode) -> Option<String> {
find_first(node, SyntaxKind::CODE_CONTENT).map(|n| n.text().to_string())
}
fn get_code_info_node(node: &crate::syntax::SyntaxNode) -> Option<crate::syntax::SyntaxNode> {
node.descendants()
.find(|element| element.kind() == SyntaxKind::CODE_INFO)
}
fn get_code_info(node: &crate::syntax::SyntaxNode) -> Option<String> {
get_code_info_node(node).map(|n| n.text().to_string())
}
#[test]
fn parses_simple_backtick_code_block() {
let input = "```\nprint(\"hello\")\n```\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "print(\"hello\")\n");
}
#[test]
fn parses_simple_tilde_code_block() {
let input = "~~~\nprint(\"hello\")\n~~~\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "print(\"hello\")\n");
}
#[test]
fn parses_code_block_with_language() {
let input = "```python\nprint(\"hello\")\n```\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "print(\"hello\")\n");
let info = get_code_info(&node).unwrap();
assert_eq!(info, "python");
}
#[test]
fn parses_code_block_with_attributes() {
let input = "```{python}\nprint(\"hello\")\n```\n";
let node = parse_blocks_quarto(input);
assert_block_kinds_for_node(&node, &[SyntaxKind::CODE_BLOCK], input);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "print(\"hello\")\n");
let info = get_code_info(&node).unwrap();
assert_eq!(info, "{python}");
}
#[test]
fn parses_code_block_with_complex_attributes() {
let input = "```{python #mycode .numberLines startFrom=\"100\"}\nprint(\"hello\")\n```\n";
let node = parse_blocks_quarto(input);
assert_block_kinds_for_node(&node, &[SyntaxKind::CODE_BLOCK], input);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "print(\"hello\")\n");
let info = get_code_info(&node).unwrap();
assert_eq!(info, "{python #mycode .numberLines startFrom=\"100\"}");
}
#[test]
fn parses_multiline_code_block() {
let input = "```python\nfor i in range(10):\n print(i)\n```\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "for i in range(10):\n print(i)\n");
}
#[test]
fn code_block_can_interrupt_paragraph() {
let input = "text\n```python\ncode\n```\n";
let node = parse_blocks(input);
assert_block_kinds_for_node(
&node,
&[SyntaxKind::PARAGRAPH, SyntaxKind::CODE_BLOCK],
input,
);
let code_content = get_code_content(&node).unwrap();
assert_eq!(code_content, "code\n");
}
#[test]
fn bare_fence_without_closing_fence_does_not_interrupt_paragraph() {
let input = "text\n```\ncode\n";
let tree = crate::parse(input, None);
let paragraphs: Vec<_> = tree
.descendants()
.filter(|n| n.kind() == SyntaxKind::PARAGRAPH)
.collect();
assert_eq!(paragraphs.len(), 1, "Should have one paragraph");
let code_block = tree
.descendants()
.find(|n| n.kind() == SyntaxKind::CODE_BLOCK);
assert!(code_block.is_none(), "Should not contain fenced code block");
}
#[test]
fn fence_with_info_without_closing_fence_is_not_code_block() {
let input = "````markdown\n";
let tree = crate::parse(input, None);
let code_block = tree
.descendants()
.find(|n| n.kind() == SyntaxKind::CODE_BLOCK);
assert!(code_block.is_none(), "Should not contain fenced code block");
}
#[test]
fn code_block_with_language_can_interrupt_paragraph() {
let input = "Some text:\n```r\na <- 1\n```\n";
let node = parse_blocks(input);
assert_block_kinds_for_node(
&node,
&[SyntaxKind::PARAGRAPH, SyntaxKind::CODE_BLOCK],
input,
);
let code_content = get_code_content(&node).unwrap();
assert_eq!(code_content, "a <- 1\n");
let info = get_code_info(&node).unwrap();
assert_eq!(info, "r");
}
#[test]
fn bare_fence_after_colon_with_command_transcript_can_interrupt_paragraph() {
let input = "Some text:\n```\n% pandoc -t plain\n```\n";
let node = parse_blocks(input);
assert_block_kinds_for_node(
&node,
&[SyntaxKind::PARAGRAPH, SyntaxKind::CODE_BLOCK],
input,
);
}
#[test]
fn bare_fence_in_list_item_with_closing_fence_can_interrupt_paragraph() {
let input = "- one\n ```\n code\n ```\n- two\n";
let node = parse_blocks(input);
let has_code_block = node
.descendants()
.any(|n| n.kind() == SyntaxKind::CODE_BLOCK);
assert!(
has_code_block,
"Expected fenced code block inside list item"
);
}
#[test]
fn adjacent_bare_fences_with_command_transcripts_parse_as_two_code_blocks() {
let input = "```\n% one\n```\n```\n% two\n```\n";
let node = parse_blocks(input);
let code_blocks = node
.descendants()
.filter(|n| n.kind() == SyntaxKind::CODE_BLOCK)
.count();
assert_eq!(code_blocks, 2);
}
#[test]
fn parses_code_block_at_start_of_document() {
let input = "```\ncode\n```\n";
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
}
#[test]
fn parses_code_block_after_blank_line() {
let input = "text\n\n```\ncode\n```\n";
let node = parse_blocks(input);
let blocks: Vec<_> = node
.descendants()
.filter(|n| matches!(n.kind(), SyntaxKind::PARAGRAPH | SyntaxKind::CODE_BLOCK))
.collect();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].kind(), SyntaxKind::PARAGRAPH);
assert_eq!(blocks[1].kind(), SyntaxKind::CODE_BLOCK);
}
#[test]
fn requires_at_least_three_fence_chars() {
let input = "``\ncode\n``\n";
let node = parse_blocks(input);
assert!(find_first(&node, SyntaxKind::CODE_BLOCK).is_none());
}
#[test]
fn closing_fence_must_have_at_least_same_length() {
let input = "````\ncode\n```\n";
let node = parse_blocks(input);
assert!(find_first(&node, SyntaxKind::CODE_BLOCK).is_none());
}
#[test]
fn closing_fence_can_be_longer() {
let input = "```\ncode\n`````\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "code\n");
}
#[test]
fn mixed_fence_chars_dont_close() {
let input = "```\ncode\n~~~\n";
let node = parse_blocks(input);
assert!(find_first(&node, SyntaxKind::CODE_BLOCK).is_none());
}
#[test]
fn empty_code_block() {
let input = "```\n```\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
assert!(get_code_content(&node).is_none());
}
#[test]
fn code_block_with_leading_spaces() {
let input = " ```python\n print(\"hello\")\n ```\n";
let node = parse_blocks(input);
assert_block_kinds(input, &[SyntaxKind::CODE_BLOCK]);
let content = get_code_content(&node).unwrap();
assert_eq!(content, " print(\"hello\")\n");
}
#[test]
fn definition_list_inline_fence_parses_as_code_block() {
let input = "Term\n: ```r\n a <- 1\n ```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK);
assert!(
code_block.is_some(),
"Expected code block inside definition list"
);
let content = get_code_content(&node).unwrap();
assert_eq!(content, " a <- 1\n");
let info = get_code_info(&node).unwrap();
assert_eq!(info, "r");
}
#[test]
fn executable_chunk_parses_hashpipe_label_as_chunk_option() {
let input = "```{r}\n#| label: foobar\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let info = get_code_info(&code_block).expect("expected code info");
assert_eq!(info, "{r}");
let has_label_option = code_block.descendants().any(|n| {
if n.kind() != SyntaxKind::CHUNK_OPTION {
return false;
}
let key = n
.children_with_tokens()
.find_map(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::CHUNK_OPTION_KEY => {
Some(t.text().to_string())
}
_ => None,
})
.unwrap_or_default();
let value = n
.children_with_tokens()
.find_map(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::CHUNK_OPTION_VALUE => {
Some(t.text().to_string())
}
_ => None,
})
.unwrap_or_default();
key == "label" && value == "foobar"
});
assert!(
has_label_option,
"expected hashpipe label to be parsed as CHUNK_OPTION"
);
}
#[test]
fn executable_chunk_keeps_non_hashpipe_lines_in_code_content() {
let input = "```{r}\n#| label: foobar\na <- 1\n# comment\n```\n";
let node = parse_blocks_quarto(input);
let content = get_code_content(&node).unwrap();
assert_eq!(content, "#| label: foobar\na <- 1\n# comment\n");
}
#[test]
fn executable_chunk_multiline_hashpipe_continuation_is_not_top_level_text() {
let input = "```{r}\n#| fig-cap: \"A multiline caption\n#| that spans multiple lines and demonstrates\n#| wrapping.\"\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let has_top_level_continuation_text = code_content.children_with_tokens().any(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::TEXT => {
t.text().trim_start().starts_with("#| ")
}
_ => false,
});
assert!(
!has_top_level_continuation_text,
"multiline hashpipe continuation should not be emitted as top-level TEXT token"
);
}
#[test]
fn executable_chunk_block_scalar_hashpipe_continuation_is_not_top_level_text() {
let input = "```{r}\n#| fig-cap: |\n#| A caption\n#| spanning some lines\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let has_top_level_continuation_text = code_content.children_with_tokens().any(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::TEXT => {
t.text().trim_start().starts_with("#| ")
}
_ => false,
});
assert!(
!has_top_level_continuation_text,
"block-scalar hashpipe continuation should not be emitted as top-level TEXT token"
);
}
#[test]
fn executable_chunk_folded_block_scalar_hashpipe_continuation_is_not_top_level_text() {
let input =
"```{r}\n#| fig-cap: >-\n#| A folded caption\n#| spanning some lines\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let has_top_level_continuation_text = code_content.children_with_tokens().any(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::TEXT => {
t.text().trim_start().starts_with("#| ")
}
_ => false,
});
assert!(
!has_top_level_continuation_text,
"folded block-scalar hashpipe continuation should not be emitted as top-level TEXT token"
);
}
#[test]
fn executable_chunk_indented_hashpipe_value_continuation_is_not_top_level_text() {
let input = "```{r}\n#| list:\n#| - a\n#| - b\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let has_top_level_continuation_text = code_content.children_with_tokens().any(|el| match el {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::TEXT => {
t.text().trim_start().starts_with("#| - ")
}
_ => false,
});
assert!(
!has_top_level_continuation_text,
"indented hashpipe continuation should not be emitted as top-level TEXT token"
);
}
#[test]
fn executable_chunk_emits_hashpipe_yaml_preamble_node() {
let input = "```{r}\n#| echo: false\n#| fig-cap: |\n#| A caption\nx <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let preamble = code_content
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
.expect("expected hashpipe preamble node");
assert_eq!(
preamble.text().to_string(),
"#| echo: false\n#| fig-cap: |\n#| A caption\n"
);
}
#[test]
fn executable_chunk_emits_hashpipe_yaml_content_node() {
let input = "```{r}\n#| echo: false\n#| fig-cap: |\n#| A caption\nx <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let preamble = code_content
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
.expect("expected hashpipe preamble node");
let preamble_content = preamble
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_CONTENT)
.expect("expected hashpipe preamble content node");
assert_eq!(
preamble_content.text().to_string(),
"#| echo: false\n#| fig-cap: |\n#| A caption\n"
);
}
#[test]
fn executable_chunk_emits_hashpipe_prefix_token_kind() {
let input = "```{r}\n#| echo: false\nx <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let preamble = code_content
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
.expect("expected hashpipe preamble node");
let prefix_tokens: Vec<_> = preamble
.descendants_with_tokens()
.filter_map(|el| el.into_token())
.filter(|t| t.kind() == SyntaxKind::HASHPIPE_PREFIX)
.map(|t| t.text().to_string())
.collect();
assert_eq!(prefix_tokens, vec!["#|".to_string()]);
}
#[test]
fn executable_chunk_hashpipe_trivia_preserves_whitespace_and_crlf() {
let input = "```{r}\r\n#| echo: false \r\nx <- 1\r\n```\r\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let preamble = code_content
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
.expect("expected hashpipe preamble node");
assert_eq!(preamble.text().to_string(), "#| echo: false \r\n");
}
#[test]
fn executable_chunk_hashpipe_continuation_preserves_trailing_space() {
let input = "```{r}\n#| fig-subcap:\n#| - \"Histogram of `price`s\"\n#| - \"Histogram of `area`s\" \nx <- 1\n```\n";
let node = parse_blocks_quarto(input);
assert_eq!(node.text().to_string(), input);
}
#[test]
fn executable_chunk_hashpipe_preamble_captures_non_option_prefixed_lines() {
let input = "```{r}\n#| fig-subcap: - \"A\"\n#| - \"B\"\nx <- 1\n```\n";
let node = parse_blocks_quarto(input);
let code_block = find_first(&node, SyntaxKind::CODE_BLOCK).expect("expected code block");
let code_content = code_block
.children()
.find(|n| n.kind() == SyntaxKind::CODE_CONTENT)
.expect("expected code content");
let preamble = code_content
.children()
.find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
.expect("expected hashpipe preamble node");
assert_eq!(
preamble.text().to_string(),
"#| fig-subcap: - \"A\"\n#| - \"B\"\n"
);
}
#[test]
fn executable_chunk_hashpipe_multiline_scalar_in_list_does_not_gain_indent() {
let input = "- Press Ctrl + `.` to open tool.\n\n ```{r}\n #| fig-cap: >\n #| Go to File/Function in RStudio.\n #| fig-alt: >\n #| Screenshot of the \"Go to File/Function\" tool in the\n #| RStudio IDE. It is a text box with the cursor in it.\n knitr::include_graphics(\"images/file-finder.png\", dpi = 220)\n ```\n";
let node = parse_blocks_quarto(input);
assert_eq!(node.text().to_string(), input);
}
#[test]
fn executable_code_respects_extension_guard() {
let input = "```{r}\na <- 1\n```\n";
let mut config = ParserOptions::default();
config.extensions.executable_code = false;
let disabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&disabled, SyntaxKind::CODE_BLOCK).is_none(),
"executable_code disabled should prevent executable chunk parsing"
);
config.extensions.executable_code = true;
let enabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&enabled, SyntaxKind::CODE_BLOCK).is_some(),
"executable_code enabled should allow executable chunk parsing"
);
}
#[test]
fn display_code_block_keeps_hashpipe_line_as_plain_text() {
let input = "```r\n#| label: foobar\na <- 1\n```\n";
let node = parse_blocks_quarto(input);
let has_chunk_option = node
.descendants()
.any(|n| n.kind() == SyntaxKind::CHUNK_OPTION);
assert!(
!has_chunk_option,
"display-only code blocks should not parse hashpipe as chunk options"
);
}
#[test]
fn backtick_fenced_code_blocks_respect_extension_guard() {
let input = "```r\na <- 1\n```\n";
let mut config = ParserOptions::default();
config.extensions.backtick_code_blocks = false;
let disabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&disabled, SyntaxKind::CODE_BLOCK).is_none(),
"backtick_code_blocks disabled should prevent backtick fenced code parsing"
);
config.extensions.backtick_code_blocks = true;
let enabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&enabled, SyntaxKind::CODE_BLOCK).is_some(),
"backtick_code_blocks enabled should allow backtick fenced code parsing"
);
}
#[test]
fn tilde_fenced_code_blocks_respect_extension_guard() {
let input = "~~~r\na <- 1\n~~~\n";
let mut config = ParserOptions::default();
config.extensions.fenced_code_blocks = false;
let disabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&disabled, SyntaxKind::CODE_BLOCK).is_none(),
"fenced_code_blocks disabled should prevent tilde fenced code parsing"
);
config.extensions.fenced_code_blocks = true;
let enabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&enabled, SyntaxKind::CODE_BLOCK).is_some(),
"fenced_code_blocks enabled should allow tilde fenced code parsing"
);
}
#[test]
fn gfm_defaults_allow_backtick_and_tilde_fenced_code_blocks() {
let backtick = parse_blocks_gfm("```r\na <- 1\n```\n");
assert!(
find_first(&backtick, SyntaxKind::CODE_BLOCK).is_some(),
"gfm defaults should allow backtick fenced code blocks"
);
let tilde = parse_blocks_gfm("~~~r\na <- 1\n~~~\n");
assert!(
find_first(&tilde, SyntaxKind::CODE_BLOCK).is_some(),
"gfm defaults should allow tilde fenced code blocks"
);
}
#[test]
fn fenced_code_attributes_respect_extension_guard() {
let input = "```{python}\na <- 1\n```\n";
let mut config = ParserOptions {
flavor: Flavor::Quarto,
extensions: Extensions::for_flavor(Flavor::Quarto),
..Default::default()
};
config.extensions.fenced_code_attributes = false;
let disabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&disabled, SyntaxKind::CODE_BLOCK).is_none(),
"fenced_code_attributes disabled should prevent brace-info fenced code parsing"
);
config.extensions.fenced_code_attributes = true;
let enabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&enabled, SyntaxKind::CODE_BLOCK).is_some(),
"fenced_code_attributes enabled should allow brace-info fenced code parsing"
);
}
#[test]
fn raw_attribute_respects_extension_guard_for_fenced_code() {
let input = "```{=html}\n<div>raw</div>\n```\n";
let mut config = ParserOptions::default();
config.extensions.raw_attribute = false;
config.extensions.fenced_code_attributes = false;
let disabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&disabled, SyntaxKind::CODE_BLOCK).is_none(),
"raw_attribute disabled should prevent raw-attribute fenced code parsing"
);
config.extensions.raw_attribute = true;
let enabled = parse_blocks_with_config(input, &config);
assert!(
find_first(&enabled, SyntaxKind::CODE_BLOCK).is_some(),
"raw_attribute enabled should allow raw-attribute fenced code parsing"
);
}
#[test]
fn tex_math_gfm_parses_math_fence_as_display_math() {
let input = "``` math\nx + y\n```\n";
let mut config = ParserOptions::default();
config.extensions.tex_math_gfm = true;
let tree = parse_blocks_with_config(input, &config);
assert!(
find_first(&tree, SyntaxKind::DISPLAY_MATH).is_some(),
"tex_math_gfm enabled should parse ``` math fences as display math"
);
}
#[test]
fn standalone_dollar_math_delimiters_do_not_split_into_tex_block() {
let input = "And so now our between group sum of squares is obtained by summing these\n\"weighted squared deviations\" over all three groups in the study:\n$$\n\\begin{aligned} SS_b & = 1.14 + 0.18 + 2.16 \\\\ &= 3.48 \\end{aligned}\n$$\n";
let tree = parse_blocks(input);
assert!(
find_first(&tree, SyntaxKind::TEX_BLOCK).is_none(),
"display math delimited by standalone $$ lines should stay paragraph-inline, not TEX_BLOCK"
);
}
#[test]
fn parses_indented_code_block() {
let input = "
code line 1
code line 2";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 1);
let code_blocks = find_all(&tree, SyntaxKind::CODE_BLOCK);
let code = &code_blocks[0];
let text = code.text().to_string();
assert!(text.contains("code line 1"));
assert!(text.contains("code line 2"));
}
#[test]
fn indented_code_block_with_blank_line() {
let input = "
code line 1
code line 2";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 1);
}
#[test]
fn indented_code_requires_blank_line_before() {
let input = "paragraph
not code";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 0);
assert_eq!(find_all(&tree, SyntaxKind::PARAGRAPH).len(), 1);
}
#[test]
fn indented_code_with_tab() {
let input = "
\tcode with tab";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 1);
}
#[test]
fn indented_code_with_list_marker() {
let input = "
* one
* two";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 1);
}
#[test]
fn indented_code_in_blockquote() {
let input = ">
> code in blockquote";
let tree = parse_blocks(input);
assert_eq!(find_all(&tree, SyntaxKind::BLOCK_QUOTE).len(), 1);
assert_eq!(find_all(&tree, SyntaxKind::CODE_BLOCK).len(), 1);
}