use crate::ast::*;
use crate::ast_transform::visitor::VisitWith;
use crate::ast_transform::Visitor;
fn create_test_doc() -> Document {
Document {
blocks: vec![
Block::Paragraph(vec![
Inline::Text("Hello ".to_string()),
Inline::Emphasis(vec![Inline::Text("world".to_string())]),
Inline::Text(" with ".to_string()),
Inline::Strong(vec![
Inline::Text("strong ".to_string()),
Inline::Code("code".to_string()),
]),
Inline::Text(" and ".to_string()),
Inline::Link(Link {
destination: "http://example.com".to_string(),
title: Some("Example".to_string()),
children: vec![Inline::Text("link".to_string())],
}),
]),
Block::Heading(Heading {
kind: HeadingKind::Atx(2),
content: vec![
Inline::Text("Heading with ".to_string()),
Inline::Strikethrough(vec![Inline::Text("strikethrough".to_string())]),
Inline::Text(" and ".to_string()),
Inline::Autolink("mailto:test@example.com".to_string()),
],
}),
Block::CodeBlock(CodeBlock {
kind: CodeBlockKind::Fenced {
info: Some("rust".to_string()),
},
literal: "fn main() { println!(\"Hello\"); }".to_string(),
}),
Block::BlockQuote(vec![Block::Paragraph(vec![
Inline::Text("Quoted text with ".to_string()),
Inline::Html("<em>HTML</em>".to_string()),
])]),
Block::List(List {
kind: ListKind::Bullet(ListBulletKind::Star),
items: vec![
ListItem {
task: None,
blocks: vec![Block::Paragraph(vec![Inline::Text(
"First item".to_string(),
)])],
},
ListItem {
task: None,
blocks: vec![Block::Paragraph(vec![
Inline::Text("Second item with ".to_string()),
Inline::LinkReference(LinkReference {
label: vec![Inline::Text("ref".to_string())],
text: vec![Inline::Text("reference".to_string())],
}),
])],
},
],
}),
Block::Table(Table {
rows: vec![
vec![
vec![Inline::Text("Header 1".to_string())],
vec![Inline::Text("Header 2".to_string())],
],
vec![
vec![Inline::Text("Cell 1".to_string())],
vec![
Inline::Code("table code".to_string()),
Inline::Text(" content".to_string()),
],
],
],
alignments: vec![Alignment::Left, Alignment::Left],
}),
Block::FootnoteDefinition(FootnoteDefinition {
label: "note1".to_string(),
blocks: vec![Block::Paragraph(vec![Inline::Text(
"Footnote content".to_string(),
)])],
}),
Block::GitHubAlert(GitHubAlert {
alert_type: GitHubAlertType::Warning,
blocks: vec![Block::Paragraph(vec![Inline::Text(
"Warning message".to_string(),
)])],
}),
Block::Definition(LinkDefinition {
label: vec![
Inline::Text("definition ".to_string()),
Inline::Emphasis(vec![Inline::Text("label".to_string())]),
],
destination: "http://definition.com".to_string(),
title: Some("Definition title".to_string()),
}),
],
}
}
struct TextCollector {
texts: Vec<String>,
}
impl Visitor for TextCollector {
fn visit_inline(&mut self, inline: &Inline) {
if let Inline::Text(text) = inline {
self.texts.push(text.clone());
}
self.walk_inline(inline);
}
}
struct NodeCounter {
text_count: usize,
emphasis_count: usize,
strong_count: usize,
link_count: usize,
image_count: usize,
code_count: usize,
autolink_count: usize,
html_count: usize,
strikethrough_count: usize,
link_ref_count: usize,
footnote_ref_count: usize,
paragraph_count: usize,
heading_count: usize,
blockquote_count: usize,
list_count: usize,
table_count: usize,
code_block_count: usize,
html_block_count: usize,
thematic_break_count: usize,
footnote_def_count: usize,
github_alert_count: usize,
definition_count: usize,
}
impl NodeCounter {
fn new() -> Self {
Self {
text_count: 0,
emphasis_count: 0,
strong_count: 0,
link_count: 0,
image_count: 0,
code_count: 0,
autolink_count: 0,
html_count: 0,
strikethrough_count: 0,
link_ref_count: 0,
footnote_ref_count: 0,
paragraph_count: 0,
heading_count: 0,
blockquote_count: 0,
list_count: 0,
table_count: 0,
code_block_count: 0,
html_block_count: 0,
thematic_break_count: 0,
footnote_def_count: 0,
github_alert_count: 0,
definition_count: 0,
}
}
}
impl Visitor for NodeCounter {
fn visit_inline(&mut self, inline: &Inline) {
match inline {
Inline::Text(_) => self.text_count += 1,
Inline::Emphasis(_) => self.emphasis_count += 1,
Inline::Strong(_) => self.strong_count += 1,
Inline::Link(_) => self.link_count += 1,
Inline::Image(_) => self.image_count += 1,
Inline::Code(_) => self.code_count += 1,
Inline::Autolink(_) => self.autolink_count += 1,
Inline::Html(_) => self.html_count += 1,
Inline::Strikethrough(_) => self.strikethrough_count += 1,
Inline::LinkReference(_) => self.link_ref_count += 1,
Inline::FootnoteReference(_) => self.footnote_ref_count += 1,
Inline::LineBreak => {}
Inline::Empty => {}
}
self.walk_inline(inline);
}
fn visit_block(&mut self, block: &Block) {
match block {
Block::Paragraph(_) => self.paragraph_count += 1,
Block::Heading(_) => self.heading_count += 1,
Block::BlockQuote(_) => self.blockquote_count += 1,
Block::List(_) => self.list_count += 1,
Block::Table(_) => self.table_count += 1,
Block::CodeBlock(_) => self.code_block_count += 1,
Block::HtmlBlock(_) => self.html_block_count += 1,
Block::ThematicBreak => self.thematic_break_count += 1,
Block::FootnoteDefinition(_) => self.footnote_def_count += 1,
Block::GitHubAlert(_) => self.github_alert_count += 1,
Block::Definition(_) => self.definition_count += 1,
Block::Empty => {}
}
self.walk_block(block);
}
}
#[test]
fn test_basic_text_collection() {
let doc = create_test_doc();
let mut collector = TextCollector { texts: Vec::new() };
doc.visit_with(&mut collector);
assert!(collector.texts.contains(&"Hello ".to_string()));
assert!(collector.texts.contains(&"world".to_string()));
assert!(collector.texts.contains(&"strong ".to_string()));
assert!(collector.texts.contains(&"link".to_string()));
assert!(collector.texts.contains(&"Heading with ".to_string()));
assert!(collector.texts.contains(&"strikethrough".to_string()));
assert!(collector.texts.contains(&"Quoted text with ".to_string()));
assert!(collector.texts.contains(&"First item".to_string()));
assert!(collector.texts.contains(&"Second item with ".to_string()));
assert!(collector.texts.contains(&"ref".to_string()));
assert!(collector.texts.contains(&"reference".to_string()));
assert!(collector.texts.contains(&"Header 1".to_string()));
assert!(collector.texts.contains(&"Header 2".to_string()));
assert!(collector.texts.contains(&"Cell 1".to_string()));
assert!(collector.texts.contains(&"Footnote content".to_string()));
assert!(collector.texts.contains(&"Warning message".to_string()));
assert!(collector.texts.contains(&"definition ".to_string()));
}
#[test]
fn test_comprehensive_node_counting() {
let doc = create_test_doc();
let mut counter = NodeCounter::new();
doc.visit_with(&mut counter);
assert!(counter.text_count > 10); assert_eq!(counter.emphasis_count, 2); assert_eq!(counter.strong_count, 1);
assert_eq!(counter.link_count, 1);
assert_eq!(counter.code_count, 2); assert_eq!(counter.autolink_count, 1);
assert_eq!(counter.html_count, 1);
assert_eq!(counter.strikethrough_count, 1);
assert_eq!(counter.link_ref_count, 1);
assert_eq!(counter.footnote_ref_count, 0);
assert!(counter.paragraph_count >= 5); assert_eq!(counter.heading_count, 1);
assert_eq!(counter.blockquote_count, 1);
assert_eq!(counter.list_count, 1);
assert_eq!(counter.table_count, 1);
assert_eq!(counter.code_block_count, 1);
assert_eq!(counter.html_block_count, 0);
assert_eq!(counter.thematic_break_count, 0);
assert_eq!(counter.footnote_def_count, 1);
assert_eq!(counter.github_alert_count, 1);
assert_eq!(counter.definition_count, 1);
}
#[test]
fn test_visitor_with_empty_document() {
let doc = Document { blocks: vec![] };
let mut collector = TextCollector { texts: Vec::new() };
doc.visit_with(&mut collector);
assert!(collector.texts.is_empty());
}
#[test]
fn test_visitor_with_empty_paragraph() {
let doc = Document {
blocks: vec![Block::Paragraph(vec![])],
};
let mut collector = TextCollector { texts: Vec::new() };
doc.visit_with(&mut collector);
assert!(collector.texts.is_empty());
}
#[test]
fn test_visitor_with_only_empty_inlines() {
let doc = Document {
blocks: vec![Block::Paragraph(vec![Inline::Empty, Inline::Empty])],
};
let mut collector = TextCollector { texts: Vec::new() };
doc.visit_with(&mut collector);
assert!(collector.texts.is_empty());
}
#[test]
fn test_visitor_deep_nesting() {
let doc = Document {
blocks: vec![Block::BlockQuote(vec![Block::List(List {
kind: ListKind::Bullet(ListBulletKind::Dash),
items: vec![ListItem {
task: None,
blocks: vec![Block::BlockQuote(vec![Block::Paragraph(vec![
Inline::Link(Link {
destination: "http://example.com".to_string(),
title: None,
children: vec![Inline::Strong(vec![Inline::Emphasis(vec![Inline::Text(
"Deeply nested text".to_string(),
)])])],
}),
])])],
}],
})])],
};
let mut collector = TextCollector { texts: Vec::new() };
doc.visit_with(&mut collector);
assert_eq!(collector.texts.len(), 1);
assert_eq!(collector.texts[0], "Deeply nested text");
}
#[test]
fn test_visitor_with_footnote_reference() {
let doc = Document {
blocks: vec![Block::Paragraph(vec![
Inline::Text("Text with footnote".to_string()),
Inline::FootnoteReference("note1".to_string()),
])],
};
let mut counter = NodeCounter::new();
doc.visit_with(&mut counter);
assert_eq!(counter.text_count, 1);
assert_eq!(counter.footnote_ref_count, 1);
}
#[test]
fn test_visitor_with_thematic_break() {
let doc = Document {
blocks: vec![
Block::Paragraph(vec![Inline::Text("Before".to_string())]),
Block::ThematicBreak,
Block::Paragraph(vec![Inline::Text("After".to_string())]),
],
};
let mut counter = NodeCounter::new();
doc.visit_with(&mut counter);
assert_eq!(counter.paragraph_count, 2);
assert_eq!(counter.thematic_break_count, 1);
assert_eq!(counter.text_count, 2);
}
#[test]
fn test_visitor_with_html_block() {
let doc = Document {
blocks: vec![
Block::HtmlBlock("<div>HTML content</div>".to_string()),
Block::Paragraph(vec![Inline::Html("<span>Inline HTML</span>".to_string())]),
],
};
let mut counter = NodeCounter::new();
doc.visit_with(&mut counter);
assert_eq!(counter.html_block_count, 1);
assert_eq!(counter.html_count, 1);
assert_eq!(counter.paragraph_count, 1);
}