use super::output::Block;
pub fn parse_content(markdown: &str, start_line: usize) -> Vec<Block> {
turbovault_parser::parse_blocks_from_line(markdown, start_line)
}
pub fn slugify(text: &str) -> String {
turbovault_parser::slugify(text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_paragraph() {
let markdown = "This is a simple paragraph.";
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1);
assert!(matches!(blocks[0], Block::Paragraph { .. }));
if let Block::Paragraph { content, .. } = &blocks[0] {
assert_eq!(content, "This is a simple paragraph.");
}
}
#[test]
fn test_parse_heading() {
let markdown = "# Hello World";
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1);
if let Block::Heading {
level,
content,
anchor,
..
} = &blocks[0]
{
assert_eq!(*level, 1);
assert_eq!(content, "Hello World");
assert_eq!(anchor.as_deref(), Some("hello-world"));
} else {
panic!("Expected Heading block");
}
}
#[test]
fn test_parse_code_block() {
let markdown = "```rust\nfn main() {}\n```";
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1);
if let Block::Code {
language, content, ..
} = &blocks[0]
{
assert_eq!(language.as_deref(), Some("rust"));
assert_eq!(content, "fn main() {}");
} else {
panic!("Expected Code block");
}
}
#[test]
fn test_wikilinks_rendered_as_links() {
let markdown = "Here is a [[wikilink]] and [[target|alias]] test.";
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1);
if let Block::Paragraph { inline, .. } = &blocks[0] {
use turbovault_parser::InlineElement;
let links: Vec<_> = inline
.iter()
.filter_map(|e| {
if let InlineElement::Link { text, url, .. } = e {
Some((text.clone(), url.clone()))
} else {
None
}
})
.collect();
assert_eq!(links.len(), 2, "Should have 2 wikilinks");
assert_eq!(links[0].0, "wikilink");
assert_eq!(links[0].1, "wikilink:wikilink");
assert_eq!(links[1].0, "alias");
assert_eq!(links[1].1, "wikilink:target");
} else {
panic!("Expected Paragraph block");
}
}
#[test]
fn test_code_block_excludes_wikilinks() {
let markdown = r#"
Normal [[Valid Link]] here.
```rust
// Code block
let link = "[[Fake Link Inside Code]]";
```
Also [[Another Valid]]
"#;
let blocks = parse_content(markdown, 0);
let mut wikilink_count = 0;
for block in &blocks {
if let Block::Paragraph { inline, .. } = block {
use turbovault_parser::InlineElement;
for elem in inline {
if let InlineElement::Link { url, .. } = elem
&& url.starts_with("wikilink:")
{
wikilink_count += 1;
}
}
}
}
assert_eq!(
wikilink_count, 2,
"Should find exactly 2 wikilinks (not the one in code block)"
);
}
#[test]
fn test_list_with_code_block() {
let markdown = r#"1. Test1:
```
test1
```
2. Test2:
test2"#;
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1);
if let Block::List { ordered, items } = &blocks[0] {
assert!(ordered, "Should be an ordered list");
assert_eq!(items.len(), 2, "Should have 2 items");
assert_eq!(items[0].content, "Test1:");
assert_eq!(
items[0].blocks.len(),
1,
"First item should have 1 nested block"
);
if let Block::Code { content, .. } = &items[0].blocks[0] {
assert_eq!(content, "test1");
} else {
panic!("Expected Code block in first item");
}
assert!(items[1].content.contains("Test2:"));
assert!(items[1].content.contains("test2"));
assert!(
items[1].blocks.is_empty(),
"Second item should have no nested blocks"
);
} else {
panic!("Expected List block");
}
}
#[test]
fn test_slugify() {
assert_eq!(slugify("Hello World"), "hello-world");
assert_eq!(slugify("API Reference"), "api-reference");
assert_eq!(slugify("1. Getting Started"), "1-getting-started");
}
#[test]
fn test_details_block_with_table() {
let markdown = r#"<details>
<summary><strong>Navigation</strong></summary>
| Key | Action |
|-----|--------|
| `j` / `k` | Move down/up |
| `g` | Jump to top |
</details>"#;
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 1, "Should have 1 details block");
if let Block::Details {
summary,
blocks: nested,
..
} = &blocks[0]
{
assert_eq!(summary, "<strong>Navigation</strong>");
assert!(
!nested.is_empty(),
"Details should have nested blocks (table)"
);
let has_table = nested.iter().any(|b| matches!(b, Block::Table { .. }));
assert!(has_table, "Details should contain a table");
} else {
panic!("Expected Details block, got {:?}", blocks[0]);
}
}
#[test]
fn test_heading_inline_code_not_leaked_to_paragraph() {
let markdown = "### Convert `foo` to `bar`\n\nReplace `baz` with a redirect:";
let blocks = parse_content(markdown, 0);
assert_eq!(blocks.len(), 2);
if let Block::Heading { inline, .. } = &blocks[0] {
let code_values: Vec<&str> = inline
.iter()
.filter_map(|e| {
if let turbovault_parser::InlineElement::Code { value } = e {
Some(value.as_str())
} else {
None
}
})
.collect();
assert_eq!(
code_values,
vec!["foo", "bar"],
"heading inline should contain both Code elements"
);
} else {
panic!("Expected Heading block, got {:?}", blocks[0]);
}
if let Block::Paragraph { content, inline } = &blocks[1] {
assert!(
content.starts_with("Replace"),
"paragraph content should start with 'Replace', got: {content:?}"
);
if let Some(first) = inline.first() {
assert!(
matches!(first, turbovault_parser::InlineElement::Text { .. }),
"paragraph should start with Text, not leaked Code: {first:?}"
);
}
} else {
panic!("Expected Paragraph block, got {:?}", blocks[1]);
}
}
}