//! Smoke test for the AST visitor: build a small document by hand and //! confirm a Visitor descends into every nested node exactly once. use std::collections::HashMap; use nuwiki_core::ast::{ BlockNode, BoldNode, DocumentNode, ExternalLinkNode, HeadingNode, InlineNode, ListItemNode, ListNode, ListSymbol, ParagraphNode, Span, TableCellNode, TableNode, TableRowNode, TextNode, TransclusionNode, Visitor, }; #[derive(Default)] struct Counter { headings: usize, paragraphs: usize, text: usize, bold: usize, list_items: usize, table_cells: usize, external_links: usize, transclusions: usize, blocks: usize, inlines: usize, } impl Visitor for Counter { fn visit_block(&mut self, node: &BlockNode) { self.blocks += 1; nuwiki_core::ast::walk_block(self, node); } fn visit_inline(&mut self, node: &InlineNode) { self.inlines += 1; nuwiki_core::ast::walk_inline(self, node); } fn visit_heading(&mut self, node: &HeadingNode) { self.headings += 1; for child in &node.children { self.visit_inline(child); } } fn visit_paragraph(&mut self, node: &ParagraphNode) { self.paragraphs += 1; for child in &node.children { self.visit_inline(child); } } fn visit_text(&mut self, _: &TextNode) { self.text += 1; } fn visit_bold(&mut self, node: &BoldNode) { self.bold += 1; for child in &node.children { self.visit_inline(child); } } fn visit_list_item(&mut self, node: &ListItemNode) { self.list_items += 1; nuwiki_core::ast::walk_list_item(self, node); } fn visit_table_cell(&mut self, node: &TableCellNode) { self.table_cells += 1; for child in &node.children { self.visit_inline(child); } } fn visit_external_link(&mut self, node: &ExternalLinkNode) { self.external_links += 1; if let Some(desc) = &node.description { for child in desc { self.visit_inline(child); } } } fn visit_transclusion(&mut self, _: &TransclusionNode) { self.transclusions += 1; } } fn text(s: &str) -> InlineNode { InlineNode::Text(TextNode { span: Span::default(), content: s.into(), }) } #[test] fn visitor_descends_into_every_node() { // = Title with *bold* word = let heading = BlockNode::Heading(HeadingNode { span: Span::default(), level: 1, centered: false, children: vec![ text("Title with "), InlineNode::Bold(BoldNode { span: Span::default(), children: vec![text("bold")], }), text(" word"), ], }); // A paragraph containing an external link with two text children in its // description, and a transclusion sibling. let paragraph = BlockNode::Paragraph(ParagraphNode { span: Span::default(), children: vec![ InlineNode::ExternalLink(ExternalLinkNode { span: Span::default(), url: "https://example.com".into(), description: Some(vec![text("see "), text("docs")]), }), InlineNode::Transclusion(TransclusionNode { span: Span::default(), url: "image.png".into(), alt: None, attrs: HashMap::new(), }), ], }); // - one // - two let list = BlockNode::List(ListNode { span: Span::default(), ordered: false, symbol: ListSymbol::Dash, items: vec![ ListItemNode { span: Span::default(), symbol: ListSymbol::Dash, level: 0, checkbox: None, children: vec![text("one")], sublist: None, }, ListItemNode { span: Span::default(), symbol: ListSymbol::Dash, level: 0, checkbox: None, children: vec![text("two")], sublist: None, }, ], }); // | a | b | let table = BlockNode::Table(TableNode { span: Span::default(), has_header: false, alignments: Vec::new(), rows: vec![TableRowNode { span: Span::default(), is_header: false, cells: vec![ TableCellNode { span: Span::default(), children: vec![text("a")], col_span: false, row_span: false, }, TableCellNode { span: Span::default(), children: vec![text("b")], col_span: false, row_span: false, }, ], }], }); let doc = DocumentNode { span: Span::default(), metadata: Default::default(), children: vec![heading, paragraph, list, table], }; let mut counter = Counter::default(); counter.visit_document(&doc); // Block-level: 4 top-level + 2 list items aren't blocks assert_eq!(counter.blocks, 4, "top-level blocks"); assert_eq!(counter.headings, 1); assert_eq!(counter.paragraphs, 1); assert_eq!(counter.list_items, 2); // 1 cell row with 2 cells assert_eq!(counter.table_cells, 2); // Inlines: // heading: 3 ("Title with ", Bold, " word") + 1 (bold child) = 4 // paragraph: 2 (ExternalLink, Transclusion) + 2 (link desc) = 4 // list items: 2 (one each) // table cells: 2 (one each) // total: 12 assert_eq!(counter.inlines, 12); // Text leaves: heading 3, link description 2, list items 2, table cells 2 = 9 assert_eq!(counter.text, 9, "Text leaves"); assert_eq!(counter.bold, 1); assert_eq!(counter.external_links, 1); assert_eq!(counter.transclusions, 1); } #[test] fn document_default_is_empty() { let doc = DocumentNode::default(); assert!(doc.children.is_empty()); assert_eq!(doc.metadata.title, None); assert!(!doc.metadata.nohtml); let mut counter = Counter::default(); counter.visit_document(&doc); assert_eq!(counter.blocks, 0); assert_eq!(counter.inlines, 0); }