215 lines
5.9 KiB
Rust
215 lines
5.9 KiB
Rust
//! bds-editor PoC integration test.
|
|
//!
|
|
//! M0 validation: verifies that the editor components (buffer + highlighter)
|
|
//! work together — highlighted markdown rendering, keyboard input simulation,
|
|
//! and cursor movement. The Iced widget (CodeEditor) requires a renderer and
|
|
//! cannot be tested without a windowing system; this test covers the
|
|
//! non-GUI integration surface.
|
|
|
|
use bds_editor::{EditorBuffer, Highlighter};
|
|
|
|
// ── PoC: highlighted markdown rendering ──
|
|
|
|
#[test]
|
|
fn highlight_markdown_document() {
|
|
let md = "# Title\n\nA paragraph with **bold** text.\n\n- item one\n- item two\n";
|
|
let hl = Highlighter::new();
|
|
let syntax = hl.syntax_for_extension("md");
|
|
let lines = hl.highlight_lines(md, syntax);
|
|
|
|
// 6 content lines in the markdown
|
|
assert_eq!(lines.len(), 6);
|
|
// Each line has at least one styled span
|
|
for (i, line) in lines.iter().enumerate() {
|
|
assert!(!line.is_empty(), "line {i} should have styled spans");
|
|
}
|
|
// The heading line should contain "# Title"
|
|
let heading_text: String = lines[0].iter().map(|(_, t)| t.as_str()).collect();
|
|
assert!(
|
|
heading_text.contains("# Title"),
|
|
"heading line should contain '# Title', got: {heading_text}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn highlight_multiple_syntaxes() {
|
|
let hl = Highlighter::new();
|
|
|
|
// Markdown
|
|
let md_syntax = hl.syntax_for_extension("md");
|
|
let md_lines = hl.highlight_lines("# Hello", md_syntax);
|
|
assert_eq!(md_lines.len(), 1);
|
|
|
|
// YAML (used in frontmatter)
|
|
let yaml_syntax = hl.syntax_for_extension("yaml");
|
|
let yaml_lines = hl.highlight_lines("key: value", yaml_syntax);
|
|
assert_eq!(yaml_lines.len(), 1);
|
|
|
|
// HTML (used in templates)
|
|
let html_syntax = hl.syntax_for_extension("html");
|
|
let html_lines = hl.highlight_lines("<div>hello</div>", html_syntax);
|
|
assert_eq!(html_lines.len(), 1);
|
|
}
|
|
|
|
// ── PoC: keyboard input simulation ──
|
|
|
|
#[test]
|
|
fn type_text_into_buffer() {
|
|
let mut buf = EditorBuffer::new("");
|
|
// Simulate typing "Hello, world!"
|
|
for c in "Hello, world!".chars() {
|
|
buf.insert(&c.to_string());
|
|
}
|
|
assert_eq!(buf.text(), "Hello, world!");
|
|
assert_eq!(buf.cursor(), (0, 13));
|
|
}
|
|
|
|
#[test]
|
|
fn type_multiline_text() {
|
|
let mut buf = EditorBuffer::new("");
|
|
buf.insert("line one");
|
|
buf.insert("\n");
|
|
buf.insert("line two");
|
|
buf.insert("\n");
|
|
buf.insert("line three");
|
|
|
|
assert_eq!(buf.line_count(), 3);
|
|
assert_eq!(buf.cursor(), (2, 10));
|
|
|
|
let text = buf.text();
|
|
assert!(text.contains("line one\n"));
|
|
assert!(text.contains("line two\n"));
|
|
assert!(text.contains("line three"));
|
|
}
|
|
|
|
#[test]
|
|
fn backspace_and_retype() {
|
|
let mut buf = EditorBuffer::new("");
|
|
buf.insert("Helo");
|
|
// Fix typo: backspace removes 'o', then type correct chars
|
|
buf.backspace(); // "Hel" cursor at 3
|
|
buf.insert("lo"); // "Hello"
|
|
assert_eq!(buf.text(), "Hello");
|
|
}
|
|
|
|
#[test]
|
|
fn delete_forward_mid_line() {
|
|
let mut buf = EditorBuffer::new("abcdef");
|
|
buf.set_cursor(0, 3); // after 'c'
|
|
buf.delete_forward(); // remove 'd'
|
|
assert_eq!(buf.text(), "abcef");
|
|
assert_eq!(buf.cursor(), (0, 3));
|
|
}
|
|
|
|
// ── PoC: cursor movement ──
|
|
|
|
#[test]
|
|
fn cursor_navigation_full_cycle() {
|
|
let mut buf = EditorBuffer::new("first line\nsecond line\nthird line");
|
|
|
|
// Start at (0,0), move to end of first line
|
|
buf.move_end();
|
|
assert_eq!(buf.cursor(), (0, 10));
|
|
|
|
// Move down to second line
|
|
buf.move_down();
|
|
assert_eq!(buf.cursor(), (1, 10));
|
|
|
|
// Move home
|
|
buf.move_home();
|
|
assert_eq!(buf.cursor(), (1, 0));
|
|
|
|
// Move down to third line
|
|
buf.move_down();
|
|
assert_eq!(buf.cursor(), (2, 0));
|
|
|
|
// Move right 5 times
|
|
for _ in 0..5 {
|
|
buf.move_right();
|
|
}
|
|
assert_eq!(buf.cursor(), (2, 5));
|
|
|
|
// Move up twice to first line
|
|
buf.move_up();
|
|
buf.move_up();
|
|
assert_eq!(buf.cursor(), (0, 5));
|
|
|
|
// Move left to beginning with wrap
|
|
for _ in 0..6 {
|
|
buf.move_left();
|
|
}
|
|
assert_eq!(buf.cursor(), (0, 0));
|
|
}
|
|
|
|
#[test]
|
|
fn cursor_wrap_across_lines() {
|
|
let mut buf = EditorBuffer::new("abc\ndef");
|
|
|
|
// Move right past end of first line wraps to second
|
|
buf.set_cursor(0, 3);
|
|
buf.move_right();
|
|
assert_eq!(buf.cursor(), (1, 0));
|
|
|
|
// Move left at start of second line wraps to first
|
|
buf.move_left();
|
|
assert_eq!(buf.cursor(), (0, 3));
|
|
}
|
|
|
|
// ── PoC: scrolling ──
|
|
|
|
#[test]
|
|
fn scroll_keeps_cursor_visible() {
|
|
// 20 lines of content
|
|
let text: String = (0..20).map(|i| format!("line {i}\n")).collect();
|
|
let mut buf = EditorBuffer::new(&text);
|
|
|
|
// Simulate moving cursor to line 15
|
|
for _ in 0..15 {
|
|
buf.move_down();
|
|
}
|
|
assert_eq!(buf.cursor().0, 15);
|
|
|
|
// With a viewport of 10 lines, ensure scroll tracks
|
|
buf.ensure_cursor_visible(10);
|
|
let scroll = buf.scroll_offset();
|
|
assert!(
|
|
scroll <= 15 && 15 < scroll + 10,
|
|
"cursor line 15 should be visible in viewport starting at {scroll}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn scroll_by_manual() {
|
|
let text: String = (0..50).map(|i| format!("line {i}\n")).collect();
|
|
let mut buf = EditorBuffer::new(&text);
|
|
|
|
buf.scroll_by(10);
|
|
assert_eq!(buf.scroll_offset(), 10);
|
|
|
|
buf.scroll_by(-3);
|
|
assert_eq!(buf.scroll_offset(), 7);
|
|
}
|
|
|
|
// ── PoC: edit + highlight round-trip ──
|
|
|
|
#[test]
|
|
fn edit_then_rehighlight() {
|
|
let mut buf = EditorBuffer::new("# Hello\n\nSome text.");
|
|
let hl = Highlighter::new();
|
|
let syntax = hl.syntax_for_extension("md");
|
|
|
|
// Initial highlight
|
|
let lines1 = hl.highlight_lines(&buf.text(), syntax);
|
|
assert_eq!(lines1.len(), 3);
|
|
|
|
// Edit: add a new line
|
|
buf.set_cursor(2, 10);
|
|
buf.insert("\n\n## Subheading");
|
|
|
|
// Re-highlight after edit
|
|
let lines2 = hl.highlight_lines(&buf.text(), syntax);
|
|
assert_eq!(lines2.len(), 5); // 3 original + blank + subheading
|
|
let subheading_text: String = lines2[4].iter().map(|(_, t)| t.as_str()).collect();
|
|
assert!(subheading_text.contains("## Subheading"));
|
|
}
|