//! End-to-end tests for the HTML renderer. //! //! Pipeline: source text → `VimwikiSyntax::parse` → `HtmlRenderer::render`. use nuwiki_core::ast::{ BlockNode, DocumentNode, InlineNode, LinkTarget, Span, TableCellNode, TableNode, TableRowNode, TextNode, }; use nuwiki_core::render::{HtmlRenderer, Renderer}; use nuwiki_core::syntax::vimwiki::VimwikiSyntax; use nuwiki_core::syntax::SyntaxPlugin; fn render(src: &str) -> String { let doc = VimwikiSyntax::new().parse(src); HtmlRenderer::new().render_to_string(&doc).unwrap() } fn span_cell(text: &str, col_span: bool, row_span: bool) -> TableCellNode { let s = Span::default(); TableCellNode { span: s, children: vec![InlineNode::Text(TextNode { span: s, content: text.into(), })], col_span, row_span, } } fn span_table(rows: Vec>, has_header: bool) -> DocumentNode { let span = Span::default(); let rows: Vec = rows .into_iter() .enumerate() .map(|(i, cells)| TableRowNode { span, cells, is_header: has_header && i == 0, }) .collect(); DocumentNode { span, metadata: Default::default(), children: vec![BlockNode::Table(TableNode { span, rows, has_header, alignments: Vec::new(), })], } } // ===== Block elements ===== #[test] fn empty_document_renders_to_empty_string() { assert_eq!(render(""), ""); } #[test] fn heading_levels() { for level in 1..=6 { let bars = "=".repeat(level); let out = render(&format!("{bars} Title {bars}\n")); // vimwiki structure:
//
. A lone heading has hier == flat. assert!(out.starts_with(&format!( "" )), "got: {out}"); } } #[test] fn centered_heading_gets_centered_class() { let out = render(" = T =\n"); assert!(out.contains("

T

")); } #[test] fn heading_keyword_renders_as_plain_text_not_badge() { // `== TODO ==` is a section title, not an inline keyword: it must render as // a normal header (with a correct, non-empty id), not a `` // badge. Regression for the exported header losing its header styling + id. let out = render("== TODO ==\n"); assert!( out.contains( "" ), "got: {out}" ); assert!(!out.contains("class=\"todo\""), "no keyword badge: {out}"); // A keyword mid-title keeps its text in the id (no dropped word / double // space) and still renders plainly. let out2 = render("= My TODO list =\n"); assert!(out2.contains("id=\"My TODO list\""), "got: {out2}"); assert!(!out2.contains("class=\"todo\""), "got: {out2}"); } #[test] fn wikilink_description_keeps_inner_brackets() { // A `]` inside a wikilink description (e.g. a `[TICKET-1]`) must not close // the link early. Regression for descriptions with brackets being mangled. let out = render("[[page|Task [B-1]]]\n"); assert!( out.contains("Task [B-1]"), "got: {out}" ); // Two bracketed links on one line both resolve. let out2 = render("[[a|X [1]]] and [[b|Y [2]]]\n"); assert!(out2.contains("X [1]"), "got: {out2}"); assert!(out2.contains("Y [2]"), "got: {out2}"); } #[test] fn paragraph_wraps_inline_content() { let out = render("Hello world\n"); assert_eq!(out.trim(), "

Hello world

"); } #[test] fn horizontal_rule() { let out = render("----\n"); assert!(out.contains("
")); } #[test] fn blockquote_lines_become_paragraphs() { let out = render("> first\n> second\n"); assert!(out.contains("
")); assert!(out.contains("

first

")); assert!(out.contains("

second

")); } #[test] fn preformatted_block_emits_pre_with_raw_fence_attrs() { // vimwiki drops the verbatim fence text into the
 tag (`{{{rust` →
    // `
`), with no inner  wrapper.
    let out = render("{{{rust\nfn main() {}\n}}}\n");
    assert!(out.contains("
"), "got: {out}");
    assert!(out.contains("fn main() {}"));
    assert!(out.contains("
")); assert!(!out.contains("plain"), "got: {out}"); } #[test] fn preformatted_block_keeps_full_attr_string() { let out = render("{{{class=\"brush: python\"\nx\n}}}\n"); assert!(out.contains("
"), "got: {out}");
}

#[test]
fn math_block_emits_div() {
    let out = render("{{$\nx=1\n}}$\n");
    assert!(out.contains("
")); assert!(out.contains("x=1")); } #[test] fn unordered_list_with_checkbox() { let out = render("- [X] done\n- not done\n"); assert!(out.contains("
    ")); // vimwiki-compatible: class on the
  • , no (the stylesheet draws it). assert!(out.contains("
  • done
  • "), "{out}"); assert!(out.contains("
  • not done
  • "), "{out}"); assert!(!out.contains("a"), "{out}"); assert!(out.contains("
  • b
  • "), "{out}"); assert!(out.contains("
  • c
  • "), "{out}"); assert!(out.contains("
  • d
  • "), "{out}"); assert!(out.contains("
  • e
  • "), "{out}"); assert!(out.contains("
  • f
  • "), "{out}"); assert!(!out.contains("task-"), "no legacy task-* classes: {out}"); assert!(!out.contains("")); assert!(out.contains("
  • first
  • ")); } #[test] fn nested_list_renders_recursively() { let out = render("- top\n - nested\n"); assert!(out.contains("