Files
nuwiki/crates/nuwiki-lsp/tests/folding.rs
T
gffranco 21b485c91b Remove stale phase/spec references from code comments
Strip "Phase N", "SPEC §X.Y", and "v1.x" planning labels from comments
across the Rust crates and editor layers now that those tracking
artifacts are gone. Comments only — no logic, strings, or test names
touched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:25:51 -03:00

112 lines
3.8 KiB
Rust

//! LSP folding-range provider.
//!
//! Drives `folding::folding_ranges` directly. The handler wiring is
//! shallow — it just calls into this function — so we verify the
//! shapes here and trust the integration via the LSP test harness in
//! `lsp_helpers.rs`.
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
use nuwiki_core::syntax::SyntaxPlugin;
use nuwiki_lsp::folding;
fn parse(src: &str) -> nuwiki_core::ast::DocumentNode {
VimwikiSyntax::new().parse(src)
}
#[test]
fn no_headings_no_lists_emits_no_folds() {
let src = "just text\non two lines\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
assert!(folds.is_empty());
}
#[test]
fn single_heading_folds_to_end_of_document() {
let src = "= Title =\nbody line 1\nbody line 2\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
assert_eq!(folds.len(), 1);
assert_eq!(folds[0].start_line, 0);
// body line 2 is line 2; trailing newline pushes line_count to 4 so
// last_line == 3 (the empty line after the final newline).
assert!(folds[0].end_line >= 2);
}
#[test]
fn sibling_headings_each_get_their_own_fold() {
let src = "= One =\nbody\n= Two =\nbody2\n= Three =\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
let heading_folds: Vec<_> = folds
.iter()
.filter(|f| f.start_line == 0 || f.start_line == 2 || f.start_line == 4)
.collect();
assert_eq!(heading_folds.len(), 3, "got: {folds:?}");
// One closes before Two
assert_eq!(heading_folds[0].end_line, 1);
// Two closes before Three
assert_eq!(heading_folds[1].end_line, 3);
}
#[test]
fn nested_heading_folds_inside_parent() {
let src = "= Outer =\nintro\n== Inner ==\nbody\n= Other =\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
// Outer fold spans up to the line before `= Other =` (line 4).
let outer = folds
.iter()
.find(|f| f.start_line == 0)
.expect("outer fold");
assert_eq!(outer.end_line, 3);
// Inner fold is bounded by the next h1 too (since the only following
// heading is h1, which has level <= 2).
let inner = folds
.iter()
.find(|f| f.start_line == 2)
.expect("inner fold");
assert_eq!(inner.end_line, 3);
}
#[test]
fn top_level_list_gets_its_own_fold() {
let src = "before\n- item one\n- item two\n- item three\nafter\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
let list_fold = folds.iter().find(|f| f.start_line == 1);
assert!(list_fold.is_some(), "expected a list fold: {folds:?}");
}
#[test]
fn single_line_blocks_are_not_folded() {
let src = "= Solo =\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
// A heading on the only line of the document covers nothing
// foldable, so it's omitted.
let none_for_solo = folds
.iter()
.all(|f| !(f.start_line == 0 && f.end_line == 0));
assert!(none_for_solo, "got: {folds:?}");
}
#[test]
fn fold_kind_is_region() {
use tower_lsp::lsp_types::FoldingRangeKind;
let src = "= H =\nbody\n";
let ast = parse(src);
let folds = folding::folding_ranges(&ast, folding::line_count(src));
assert!(!folds.is_empty());
assert_eq!(folds[0].kind, Some(FoldingRangeKind::Region));
}
#[test]
fn line_count_matches_intuitive_definition() {
assert_eq!(folding::line_count(""), 0);
assert_eq!(folding::line_count("one"), 1);
assert_eq!(folding::line_count("one\n"), 2); // trailing newline → virtual empty line
assert_eq!(folding::line_count("one\ntwo"), 2);
assert_eq!(folding::line_count("one\ntwo\n"), 3);
}