Files
nuwiki-rs/crates/nuwiki-core/tests/lists.rs
T
gffranco 29a4efb6bc
CI / cargo fmt --check (push) Successful in 59s
CI / cargo clippy (push) Successful in 1m20s
CI / cargo test (push) Successful in 1m29s
CI / editor keymaps (push) Failing after 33s
feat: initial nuwiki Rust workspace (core, lsp, ls)
2026-06-24 00:01:59 +00:00

109 lines
3.7 KiB
Rust

//! Cluster 8b — multi-line list-item continuation.
//!
//! Lines indented strictly past the item's marker attach to the item
//! rather than becoming a sibling paragraph. Mirrors upstream vimwiki
//! + Markdown.
use nuwiki_core::ast::{BlockNode, InlineNode};
use nuwiki_core::render::{HtmlRenderer, Renderer};
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
use nuwiki_core::syntax::SyntaxPlugin;
fn parse(src: &str) -> nuwiki_core::ast::DocumentNode {
VimwikiSyntax::new().parse(src)
}
fn single_list(doc: &nuwiki_core::ast::DocumentNode) -> &nuwiki_core::ast::ListNode {
match &doc.children[0] {
BlockNode::List(l) => l,
other => panic!("expected list, got {other:?}"),
}
}
fn text_of(inlines: &[InlineNode]) -> String {
let mut out = String::new();
for n in inlines {
match n {
InlineNode::Text(t) => out.push_str(&t.content),
InlineNode::Bold(b) => out.push_str(&text_of(&b.children)),
InlineNode::Italic(i) => out.push_str(&text_of(&i.children)),
// Soft breaks join continuation lines (formerly a Text(" ")).
InlineNode::SoftBreak(_) => out.push(' '),
_ => {}
}
}
out
}
#[test]
fn indented_line_after_marker_continues_the_item() {
let doc = parse("- first item\n continuing here\n- second\n");
assert_eq!(doc.children.len(), 1, "should be a single list block");
let list = single_list(&doc);
assert_eq!(list.items.len(), 2);
let first = &list.items[0];
let joined = text_of(&first.children);
assert_eq!(joined, "first item continuing here");
let second = &list.items[1];
assert_eq!(text_of(&second.children), "second");
}
#[test]
fn two_continuation_lines_both_attach() {
let doc = parse("- top\n middle\n bottom\n");
let list = single_list(&doc);
assert_eq!(list.items.len(), 1);
let joined = text_of(&list.items[0].children);
assert_eq!(joined, "top middle bottom");
}
#[test]
fn unindented_line_breaks_the_list() {
let doc = parse("- item\nflush against margin\n");
// Two blocks: a list (just `- item`) and a paragraph.
assert!(
doc.children.len() >= 2,
"want list + paragraph, got {}",
doc.children.len()
);
match &doc.children[0] {
BlockNode::List(l) => {
assert_eq!(text_of(&l.items[0].children), "item");
}
other => panic!("expected list first, got {other:?}"),
}
}
#[test]
fn blank_line_breaks_the_list_item() {
// A blank line ends the current list (paragraph semantics).
let doc = parse("- item\n\n orphaned\n");
let list = single_list(&doc);
assert_eq!(list.items.len(), 1);
assert_eq!(text_of(&list.items[0].children), "item");
}
#[test]
fn continuation_works_for_nested_lists_too() {
let doc = parse("- parent\n - child\n continued child\n- sibling\n");
let list = single_list(&doc);
assert_eq!(list.items.len(), 2);
let parent = &list.items[0];
let sublist = parent.sublist.as_ref().expect("nested list");
assert_eq!(sublist.items.len(), 1);
let child = &sublist.items[0];
assert_eq!(text_of(&child.children), "child continued child");
}
#[test]
fn html_renderer_emits_a_single_li_per_item() {
let doc = parse("- first item\n continuing here\n- second\n");
let out = HtmlRenderer::new().render_to_string(&doc).unwrap();
// Single <ul>, two <li> entries — the bug we're fixing produced
// two adjacent <ul> elements with an interleaved <p>.
assert_eq!(out.matches("<ul>").count(), 1, "got: {out}");
assert_eq!(out.matches("<li>").count(), 2, "got: {out}");
assert!(!out.contains("<p>"), "no orphan paragraph; got: {out}");
assert!(out.contains("first item continuing here"), "got: {out}");
}