2026-05-30 18:35:40 +00:00
|
|
|
//! Cluster B — table rewriters: `insert`, `align`, `moveColumn`.
|
2026-05-12 14:41:51 +00:00
|
|
|
|
|
|
|
|
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
|
|
|
|
|
use nuwiki_core::syntax::SyntaxPlugin;
|
|
|
|
|
use nuwiki_lsp::commands::ops;
|
|
|
|
|
use tower_lsp::lsp_types::Url;
|
|
|
|
|
|
|
|
|
|
fn parse(src: &str) -> nuwiki_core::ast::DocumentNode {
|
|
|
|
|
VimwikiSyntax::new().parse(src)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn uri() -> Url {
|
|
|
|
|
Url::parse("file:///tmp/tbl.wiki").unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn one_text(edit: tower_lsp::lsp_types::WorkspaceEdit) -> String {
|
|
|
|
|
let changes = edit.changes.expect("changes");
|
|
|
|
|
let (_, edits) = changes.into_iter().next().unwrap();
|
|
|
|
|
edits.into_iter().next().unwrap().new_text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== render_blank_table =====
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blank_table_has_header_separator_and_rows() {
|
|
|
|
|
let out = ops::render_blank_table(3, 2);
|
|
|
|
|
let lines: Vec<&str> = out.lines().collect();
|
|
|
|
|
// Header + separator + 2 data rows = 4.
|
|
|
|
|
assert_eq!(lines.len(), 4);
|
|
|
|
|
assert_eq!(lines[0], "| | | |");
|
|
|
|
|
assert_eq!(lines[1], "|--|--|--|");
|
|
|
|
|
assert_eq!(lines[2], "| | | |");
|
|
|
|
|
assert_eq!(lines[3], "| | | |");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blank_table_handles_single_cell_grid() {
|
|
|
|
|
let out = ops::render_blank_table(1, 1);
|
|
|
|
|
assert_eq!(out, "| |\n|--|\n| |\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== align =====
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn align_pads_short_cells_to_widest() {
|
|
|
|
|
let src = "|a|bbb|c|\n|--|--|--|\n|1|2|three|\n";
|
|
|
|
|
let ast = parse(src);
|
|
|
|
|
let edit = ops::table_align_edit(src, &ast, &uri(), 0, true).expect("edit");
|
|
|
|
|
let body = one_text(edit);
|
|
|
|
|
let lines: Vec<&str> = body.lines().collect();
|
|
|
|
|
eprintln!("rendered table:");
|
|
|
|
|
for (i, l) in lines.iter().enumerate() {
|
|
|
|
|
eprintln!(" {i}: {l:?}");
|
|
|
|
|
}
|
|
|
|
|
// Column widths: max(a,1)=1 → 1, max(bbb,2)=3, max(c,three)=5.
|
|
|
|
|
assert_eq!(lines[0], "| a | bbb | c |");
|
|
|
|
|
// Separator row is repadded with `-` runs sized `width + 2` per
|
|
|
|
|
// column (the two padding spaces of content rows).
|
|
|
|
|
assert!(lines[1].starts_with("|---|"), "got: {:?}", lines[1]);
|
|
|
|
|
assert_eq!(lines[2], "| 1 | 2 | three |");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn align_returns_none_when_cursor_off_table() {
|
|
|
|
|
let src = "paragraph\n";
|
|
|
|
|
let ast = parse(src);
|
|
|
|
|
assert!(ops::table_align_edit(src, &ast, &uri(), 0, true).is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== moveColumn =====
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn move_column_right_swaps_with_neighbour() {
|
|
|
|
|
let src = "|a|b|c|\n|--|--|--|\n|1|2|3|\n";
|
|
|
|
|
let ast = parse(src);
|
|
|
|
|
// Cursor on first column → swap right.
|
|
|
|
|
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, 1, true).expect("edit");
|
|
|
|
|
let body = one_text(edit);
|
|
|
|
|
let lines: Vec<&str> = body.lines().collect();
|
|
|
|
|
assert!(lines[0].contains("b") && lines[0].contains("a"));
|
|
|
|
|
// b should come before a after swapping col 0 → 1.
|
|
|
|
|
let line0 = lines[0];
|
|
|
|
|
let b_pos = line0.find('b').unwrap();
|
|
|
|
|
let a_pos = line0.find('a').unwrap();
|
|
|
|
|
assert!(b_pos < a_pos, "expected b before a; got {line0:?}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn move_column_left_at_zero_is_noop() {
|
|
|
|
|
let src = "|a|b|\n|--|--|\n|1|2|\n";
|
|
|
|
|
let ast = parse(src);
|
|
|
|
|
// Cursor in column 0; left would be -1 → returns None.
|
|
|
|
|
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, -1, true);
|
|
|
|
|
assert!(edit.is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn move_column_returns_none_off_table() {
|
|
|
|
|
let src = "paragraph\n";
|
|
|
|
|
let ast = parse(src);
|
|
|
|
|
assert!(ops::table_move_column_edit(src, &ast, &uri(), 0, 0, 1, true).is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== COMMANDS list =====
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn commands_list_includes_cluster_b() {
|
|
|
|
|
let names: Vec<&str> = nuwiki_lsp::commands::COMMANDS.to_vec();
|
|
|
|
|
for name in [
|
|
|
|
|
"nuwiki.table.insert",
|
|
|
|
|
"nuwiki.table.align",
|
|
|
|
|
"nuwiki.table.moveColumn",
|
|
|
|
|
] {
|
|
|
|
|
assert!(names.contains(&name), "missing: {name}");
|
|
|
|
|
}
|
|
|
|
|
}
|