//! 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); }