refactor(tests): group test files by feature, drop phase/cluster naming
Restructures the integration test layout so each file is named after
what it covers, not the implementation phase it landed in.
nuwiki-core (renames only):
vimwiki_lexer → lexer
vimwiki_parser → parser
vimwiki_tags → tags
vimwiki_table_alignment → table_alignment
diary_period → diary
list_continuation → lists
transclusion_attrs → transclusion
+ table colspan/rowspan tests moved here from parity_cluster_1
nuwiki-lsp (renames + merges + one split):
cluster_a_list_rewriters → commands_lists
cluster_b_table_rewriters → commands_tables
cluster_c_link_helpers +
phase19_followlink_creates → commands_links
phase13_rename_commands → commands_files
phase17_colorize → commands_colorize
phase17_html_export → html_export
phase15_link_health → link_health
phase18_multi_wiki → multi_wiki
phase19_folding → folding
nav → navigation
lsp_helpers → helpers
phase16_diary +
diary_frequency → diary
phase11_plumbing +
parity_cluster_1 (config) → index_and_config
tags_index_and_lsp +
phase17_backfill → commands_tags
phase14_edit_commands → split into
commands_checkboxes,
commands_headings,
commands_tasks
Net: 30 → 28 integration-test files. 456 → 455 tests (the one
removed test was a dummy `paragraph_render_is_unchanged` whose only
purpose was to keep a `ParagraphNode` import alive in
parity_cluster_1; the import is exercised elsewhere now).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
//! §13.1 Cluster A — list rewriters: `removeDone`, `renumber`,
|
||||
//! `changeSymbol`, `changeLevel`.
|
||||
|
||||
use nuwiki_core::ast::ListSymbol;
|
||||
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
|
||||
use nuwiki_core::syntax::SyntaxPlugin;
|
||||
use nuwiki_lsp::commands::ops;
|
||||
use tower_lsp::lsp_types::{Position as LspPosition, Url};
|
||||
|
||||
fn parse(src: &str) -> nuwiki_core::ast::DocumentNode {
|
||||
VimwikiSyntax::new().parse(src)
|
||||
}
|
||||
|
||||
fn one_edit_text(edit: tower_lsp::lsp_types::WorkspaceEdit) -> String {
|
||||
let changes = edit.changes.expect("changes map");
|
||||
let (_, edits) = changes.into_iter().next().expect("≥1 uri");
|
||||
edits
|
||||
.into_iter()
|
||||
.map(|e| e.new_text)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn uri() -> Url {
|
||||
Url::parse("file:///tmp/list.wiki").unwrap()
|
||||
}
|
||||
|
||||
// ===== parse_symbol + render_marker =====
|
||||
|
||||
#[test]
|
||||
fn parse_symbol_accepts_short_and_long_names() {
|
||||
assert_eq!(ops::parse_symbol("-"), Some(ListSymbol::Dash));
|
||||
assert_eq!(ops::parse_symbol("Dash"), Some(ListSymbol::Dash));
|
||||
assert_eq!(ops::parse_symbol("1."), Some(ListSymbol::Numeric));
|
||||
assert_eq!(ops::parse_symbol("Numeric"), Some(ListSymbol::Numeric));
|
||||
assert_eq!(ops::parse_symbol("a)"), Some(ListSymbol::AlphaParen));
|
||||
assert_eq!(ops::parse_symbol("i)"), Some(ListSymbol::RomanParen));
|
||||
assert!(ops::parse_symbol("nope").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_marker_handles_each_symbol() {
|
||||
assert_eq!(ops::render_marker(ListSymbol::Dash, 1), "-");
|
||||
assert_eq!(ops::render_marker(ListSymbol::Star, 1), "*");
|
||||
assert_eq!(ops::render_marker(ListSymbol::Hash, 1), "#");
|
||||
assert_eq!(ops::render_marker(ListSymbol::Numeric, 1), "1.");
|
||||
assert_eq!(ops::render_marker(ListSymbol::Numeric, 4), "4.");
|
||||
assert_eq!(ops::render_marker(ListSymbol::NumericParen, 7), "7)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::AlphaParen, 1), "a)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::AlphaParen, 26), "z)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::AlphaParen, 27), "aa)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::AlphaUpperParen, 2), "B)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::RomanParen, 4), "iv)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::RomanParen, 9), "ix)");
|
||||
assert_eq!(ops::render_marker(ListSymbol::RomanUpperParen, 14), "XIV)");
|
||||
}
|
||||
|
||||
// ===== removeDone =====
|
||||
|
||||
#[test]
|
||||
fn remove_done_strips_done_and_rejected_items() {
|
||||
let src = "- [ ] todo\n- [X] done\n- [-] rejected\n- [ ] also todo\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::remove_done_edit(src, &ast, &uri(), None, true).expect("edit");
|
||||
let changes = edit.changes.expect("changes");
|
||||
let edits = &changes[&uri()];
|
||||
assert_eq!(edits.len(), 2, "expected 2 deletions");
|
||||
// Each delete edit replaces a span with empty string.
|
||||
for e in edits {
|
||||
assert_eq!(e.new_text, "");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_done_leaves_open_items_alone() {
|
||||
let src = "- [ ] todo\n- [.] partial\n- [o] more\n";
|
||||
let ast = parse(src);
|
||||
assert!(ops::remove_done_edit(src, &ast, &uri(), None, true).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_done_returns_none_when_no_lists() {
|
||||
let src = "= heading =\nparagraph\n";
|
||||
let ast = parse(src);
|
||||
assert!(ops::remove_done_edit(src, &ast, &uri(), None, true).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_done_scoped_by_position_to_one_item() {
|
||||
// With `position` set, only items whose line range contains the
|
||||
// cursor line are considered. So a done item on line 1 is removed
|
||||
// by cursor on line 1, but a done item on line 3 is left alone.
|
||||
let src = "- [X] done one\n- [ ] todo\n- [X] done two\n";
|
||||
let ast = parse(src);
|
||||
let pos = Some(LspPosition {
|
||||
line: 0,
|
||||
character: 0,
|
||||
});
|
||||
let edit = ops::remove_done_edit(src, &ast, &uri(), pos, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
assert_eq!(edits.len(), 1, "only the line-0 item should match");
|
||||
}
|
||||
|
||||
// ===== renumber =====
|
||||
|
||||
#[test]
|
||||
fn renumber_resequences_numeric_markers() {
|
||||
let src = "5. first\n9. second\n2. third\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::renumber_edit(src, &ast, &uri(), 0, false, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
// We expect 3 marker rewrites: 5.→1., 9.→2., 2.→3.
|
||||
assert_eq!(edits.len(), 3);
|
||||
let texts: Vec<&str> = edits.iter().map(|e| e.new_text.as_str()).collect();
|
||||
assert!(texts.contains(&"1."));
|
||||
assert!(texts.contains(&"2."));
|
||||
assert!(texts.contains(&"3."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renumber_ignores_unordered_lists() {
|
||||
let src = "- a\n- b\n- c\n";
|
||||
let ast = parse(src);
|
||||
assert!(ops::renumber_edit(src, &ast, &uri(), 0, false, true).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renumber_whole_file_walks_every_list() {
|
||||
let src = "5. one\n\n2. two\n9. three\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::renumber_edit(src, &ast, &uri(), 0, true, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
// Two separate lists — first has 1 numeric item, second has 2 →
|
||||
// 3 marker rewrites total.
|
||||
assert_eq!(edits.len(), 3);
|
||||
}
|
||||
|
||||
// ===== changeSymbol =====
|
||||
|
||||
#[test]
|
||||
fn change_symbol_rewrites_just_the_current_item() {
|
||||
let src = "- a\n- b\n- c\n";
|
||||
let ast = parse(src);
|
||||
let edit =
|
||||
ops::change_symbol_edit(src, &ast, &uri(), 1, ListSymbol::Star, false, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
assert_eq!(edits.len(), 1);
|
||||
assert_eq!(edits[0].new_text, "*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_symbol_whole_list_rewrites_every_item() {
|
||||
let src = "- a\n- b\n- c\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::change_symbol_edit(src, &ast, &uri(), 1, ListSymbol::Numeric, true, true)
|
||||
.expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
assert_eq!(edits.len(), 3);
|
||||
let texts: Vec<&str> = edits.iter().map(|e| e.new_text.as_str()).collect();
|
||||
assert!(texts.contains(&"1."));
|
||||
assert!(texts.contains(&"2."));
|
||||
assert!(texts.contains(&"3."));
|
||||
}
|
||||
|
||||
// ===== changeLevel =====
|
||||
|
||||
#[test]
|
||||
fn change_level_indents_single_item() {
|
||||
let src = "- one\n- two\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::change_level_edit(src, &ast, &uri(), 1, 1, false, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
assert_eq!(edits.len(), 1);
|
||||
// delta=1 → +2 spaces
|
||||
assert_eq!(edits[0].new_text, " ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_level_dedents_clamps_to_zero() {
|
||||
let src = "- root\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::change_level_edit(src, &ast, &uri(), 0, -1, false, true);
|
||||
// Already at column 0 — dedent is a no-op.
|
||||
assert!(edit.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_level_whole_subtree_walks_descendants() {
|
||||
let src = "- parent\n - child a\n - child b\n- sibling\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::change_level_edit(src, &ast, &uri(), 0, 1, true, true).expect("edit");
|
||||
let edits = &edit.changes.unwrap()[&uri()];
|
||||
// Parent + 2 children get indented. Sibling stays.
|
||||
assert!(edits.len() >= 3, "got {} edits", edits.len());
|
||||
}
|
||||
|
||||
// ===== COMMANDS list completeness =====
|
||||
|
||||
#[test]
|
||||
fn commands_list_includes_cluster_a() {
|
||||
let names: Vec<&str> = nuwiki_lsp::commands::COMMANDS.to_vec();
|
||||
for name in [
|
||||
"nuwiki.list.removeDone",
|
||||
"nuwiki.list.renumber",
|
||||
"nuwiki.list.changeSymbol",
|
||||
"nuwiki.list.changeLevel",
|
||||
] {
|
||||
assert!(names.contains(&name), "missing: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Smoke a one_edit_text helper so it's exercised by at least one test.
|
||||
#[test]
|
||||
fn one_edit_text_round_trip() {
|
||||
let src = "- [X] done\n";
|
||||
let ast = parse(src);
|
||||
let edit = ops::remove_done_edit(src, &ast, &uri(), None, true).unwrap();
|
||||
let _ = one_edit_text(edit);
|
||||
}
|
||||
Reference in New Issue
Block a user