use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
#[test]
fn test_cursor_sync_with_non_ascii_box_drawing_chars() {
let mut harness = EditorTestHarness::new(120, 30).unwrap();
let text_with_boxes = " 17 │ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │";
harness.type_text(text_with_boxes).unwrap();
harness.render().unwrap();
harness.assert_buffer_content(text_with_boxes);
let buffer_pos = harness.cursor_position();
let expected_buffer_pos = text_with_boxes.len();
assert_eq!(
buffer_pos, expected_buffer_pos,
"Cursor should be at end of text (byte position {}), but is at {}",
expected_buffer_pos, buffer_pos
);
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
let buffer_pos_after_home = harness.cursor_position();
assert_eq!(
buffer_pos_after_home, 0,
"Cursor should be at position 0 after Home"
);
for i in 1..=10 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
let buffer_pos = harness.cursor_position();
let (screen_x, _screen_y) = harness.screen_cursor_position();
println!(
"After {} right arrows: buffer_pos={}, screen_x={}",
i, buffer_pos, screen_x
);
}
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..20 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
let buffer_pos_before_insert = harness.cursor_position();
let (screen_x_before, screen_y_before) = harness.screen_cursor_position();
println!(
"Before insert: buffer_pos={}, screen=({}, {})",
buffer_pos_before_insert, screen_x_before, screen_y_before
);
harness.type_text("X").unwrap();
let buffer_content_after = harness.get_buffer_content().unwrap();
println!("Buffer after insert: {:?}", buffer_content_after);
harness.render().unwrap();
}
#[test]
fn test_cursor_sync_with_emoji() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let text = "Hello 😀 World 🌍";
harness.type_text(text).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..7 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
let buffer_pos = harness.cursor_position();
assert_eq!(
buffer_pos, 10,
"After moving through 'Hello 😀', cursor should be at byte 10"
);
harness.type_text("X").unwrap();
let expected = "Hello 😀X World 🌍";
harness.assert_buffer_content(expected);
}
#[test]
fn test_mouse_click_on_non_ascii_text() {
let mut harness = EditorTestHarness::new(120, 30).unwrap();
let text = "│ ┌──────────────┐ ┌──────────────┐ │";
harness.type_text(text).unwrap();
harness.render().unwrap();
let _line_row = 1;
}
#[test]
fn test_backspace_deletes_entire_utf8_character() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("€").unwrap();
harness.assert_buffer_content("€");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("");
harness.type_text("æøå").unwrap();
harness.assert_buffer_content("æøå");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("æø");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("æ");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("");
harness.type_text("a😀b").unwrap();
harness.assert_buffer_content("a😀b");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("a😀");
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("a");
}
#[test]
fn test_delete_forward_removes_entire_utf8_character() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("a€b").unwrap();
harness.assert_buffer_content("a€b");
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Delete, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("€b");
harness
.send_key(KeyCode::Delete, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("b");
}
#[test]
fn test_selection_delete_with_utf8_characters() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aæøåb").unwrap();
harness.assert_buffer_content("aæøåb");
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness.assert_buffer_content("ab");
}
#[test]
fn test_selection_replace_with_utf8_characters() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello😀world").unwrap();
harness.assert_buffer_content("hello😀world");
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness.type_text("X").unwrap();
harness.assert_buffer_content("helloXworld");
}
#[test]
fn test_backspace_utf8_file_save_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let euro_path = temp_dir.path().join("euro.txt");
std::fs::write(&euro_path, "€\n").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&euro_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let saved = std::fs::read(&euro_path).unwrap();
assert_eq!(
saved, b"\n",
"Euro sign should be fully deleted, file should contain only newline. Got: {:?}",
saved
);
let norwegian_path = temp_dir.path().join("norwegian.txt");
std::fs::write(&norwegian_path, "æøå\n").unwrap();
let mut harness2 = EditorTestHarness::new(80, 24).unwrap();
harness2.open_file(&norwegian_path).unwrap();
harness2.render().unwrap();
harness2.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness2
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
harness2
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness2.render().unwrap();
let saved2 = std::fs::read(&norwegian_path).unwrap();
assert_eq!(
saved2,
"æø\n".as_bytes(),
"Only 'å' should be deleted, leaving 'æø'. Got: {:?}",
String::from_utf8_lossy(&saved2)
);
}
#[test]
fn test_thai_grapheme_cluster_movement() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let text = "aที่b";
harness.type_text(text).unwrap();
harness.render().unwrap();
harness.assert_buffer_content(text);
let pos_at_end = harness.cursor_position();
assert_eq!(
pos_at_end, 11,
"Cursor should be at byte 11 after typing text"
);
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
assert_eq!(
harness.cursor_position(),
0,
"Cursor should be at start after Home"
);
let (initial_x, initial_y) = harness.screen_cursor_position();
println!("Initial screen cursor: ({}, {})", initial_x, initial_y);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let pos1 = harness.cursor_position();
let (x1, y1) = harness.screen_cursor_position();
println!(
"After 1st Right: buffer pos={}, screen=({}, {})",
pos1, x1, y1
);
assert_eq!(pos1, 1, "After 1st Right, should be at byte 1 (after 'a')");
assert_eq!(
x1,
initial_x + 1,
"Screen cursor should advance by 1 column (past 'a')"
);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let pos2 = harness.cursor_position();
let (x2, y2) = harness.screen_cursor_position();
println!(
"After 2nd Right: buffer pos={}, screen=({}, {})",
pos2, x2, y2
);
assert_eq!(
pos2, 10,
"After 2nd Right, should be at byte 10 (after Thai cluster 'ที่'). Got {}",
pos2
);
assert_eq!(
x2,
initial_x + 2,
"Screen cursor should advance by 1 column (Thai cluster has visual width 1). Got {}",
x2
);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let pos3 = harness.cursor_position();
let (x3, y3) = harness.screen_cursor_position();
println!(
"After 3rd Right: buffer pos={}, screen=({}, {})",
pos3, x3, y3
);
assert_eq!(
pos3, 11,
"After 3rd Right, should be at byte 11 (after 'b')"
);
assert_eq!(
x3,
initial_x + 3,
"Screen cursor should advance by 1 column (past 'b')"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_l1 = harness.cursor_position();
let (xl1, _) = harness.screen_cursor_position();
println!("After 1st Left: buffer pos={}, screen x={}", pos_l1, xl1);
assert_eq!(pos_l1, 10, "After 1st Left, should be at byte 10");
assert_eq!(xl1, initial_x + 2, "Screen cursor should be at column 2");
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_l2 = harness.cursor_position();
let (xl2, _) = harness.screen_cursor_position();
println!("After 2nd Left: buffer pos={}, screen x={}", pos_l2, xl2);
assert_eq!(
pos_l2, 1,
"After 2nd Left, should be at byte 1 (before Thai cluster). Got {}",
pos_l2
);
assert_eq!(
xl2,
initial_x + 1,
"Screen cursor should be at column 1 (after 'a'). Got {}",
xl2
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_l3 = harness.cursor_position();
let (xl3, _) = harness.screen_cursor_position();
println!("After 3rd Left: buffer pos={}, screen x={}", pos_l3, xl3);
assert_eq!(pos_l3, 0, "After 3rd Left, should be at byte 0");
assert_eq!(
xl3, initial_x,
"Screen cursor should be back at initial column"
);
}
#[test]
fn test_thai_backspace_layer_by_layer() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let thai = "ที่";
harness.type_text(thai).unwrap();
harness.render().unwrap();
assert_eq!(harness.cursor_position(), 9);
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
let content1 = harness.get_buffer_content().unwrap();
assert_eq!(
content1, "ที",
"First backspace should delete only the tone mark. Got: {:?}",
content1
);
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
let content2 = harness.get_buffer_content().unwrap();
assert_eq!(
content2, "ท",
"Second backspace should delete only the vowel mark. Got: {:?}",
content2
);
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
let content3 = harness.get_buffer_content().unwrap();
assert_eq!(
content3, "",
"Third backspace should delete the base consonant. Got: {:?}",
content3
);
}
#[test]
fn test_thai_delete_entire_cluster() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let thai = "ที่นี่";
harness.type_text(thai).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
assert_eq!(harness.cursor_position(), 0);
harness
.send_key(KeyCode::Delete, KeyModifiers::NONE)
.unwrap();
let content = harness.get_buffer_content().unwrap();
assert_eq!(
content, "นี่",
"Delete should remove entire grapheme cluster 'ที่', leaving 'นี่'. Got: {:?}",
content
);
}
#[test]
fn test_thai_file_open_and_movement() {
let temp_dir = TempDir::new().unwrap();
let thai_path = temp_dir.path().join("thai.txt");
let thai_content = "ที่นี่คือที่ติดตั้งระบบ\n";
std::fs::write(&thai_path, thai_content).unwrap();
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&thai_path).unwrap();
harness.render().unwrap();
let loaded = harness.get_buffer_content().unwrap();
assert_eq!(
loaded.trim(),
thai_content.trim(),
"Thai content should be loaded correctly"
);
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let initial_pos = harness.cursor_position();
let (initial_x, initial_y) = harness.screen_cursor_position();
println!(
"Initial: buffer pos={}, screen=({}, {})",
initial_pos, initial_x, initial_y
);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let pos1 = harness.cursor_position();
let (x1, _) = harness.screen_cursor_position();
println!("After 1st Right: buffer pos={}, screen x={}", pos1, x1);
assert_eq!(
pos1, 9,
"After 1st Right, cursor should be at byte 9 (after first Thai cluster 'ที่'). Got {}",
pos1
);
assert_eq!(
x1,
initial_x + 1,
"Screen cursor should advance by 1 column. Got {} (initial was {})",
x1,
initial_x
);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let pos2 = harness.cursor_position();
let (x2, _) = harness.screen_cursor_position();
println!("After 2nd Right: buffer pos={}, screen x={}", pos2, x2);
assert_eq!(
pos2, 18,
"After 2nd Right, cursor should be at byte 18. Got {}",
pos2
);
assert_eq!(
x2,
initial_x + 2,
"Screen cursor should be at initial+2. Got {}",
x2
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_l1 = harness.cursor_position();
let (xl1, _) = harness.screen_cursor_position();
println!("After 1st Left: buffer pos={}, screen x={}", pos_l1, xl1);
assert_eq!(
pos_l1, 9,
"After 1st Left, cursor should be at byte 9. Got {}",
pos_l1
);
assert_eq!(
xl1,
initial_x + 1,
"Screen cursor should be at initial+1. Got {}",
xl1
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_l2 = harness.cursor_position();
let (xl2, _) = harness.screen_cursor_position();
println!("After 2nd Left: buffer pos={}, screen x={}", pos_l2, xl2);
assert_eq!(
pos_l2, 0,
"After 2nd Left, cursor should be at byte 0. Got {}",
pos_l2
);
assert_eq!(
xl2, initial_x,
"Screen cursor should be back at initial. Got {}",
xl2
);
}
#[test]
fn test_search_prompt_grapheme_movement() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('f'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Search:");
let thai_text = "aที่b";
harness.type_text(thai_text).unwrap();
harness.render().unwrap();
harness.assert_screen_contains(thai_text);
let (end_x, end_y) = harness.screen_cursor_position();
println!("Cursor at end: ({}, {})", end_x, end_y);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x1, _) = harness.screen_cursor_position();
println!("After 1st Left: x={}", x1);
assert_eq!(
x1,
end_x - 1,
"1st Left should move back 1 column (past 'b')"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x2, _) = harness.screen_cursor_position();
println!("After 2nd Left: x={}", x2);
assert_eq!(
x2,
end_x - 2,
"2nd Left should skip entire Thai cluster (visual width 1)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x3, _) = harness.screen_cursor_position();
println!("After 3rd Left: x={}", x3);
assert_eq!(x3, end_x - 3, "3rd Left should move before 'a'");
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let (final_x, _) = harness.screen_cursor_position();
assert_eq!(
final_x, end_x,
"3 Right arrows should return cursor to end position"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
}
#[test]
fn test_file_open_prompt_grapheme_movement() {
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Open file:");
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::End, KeyModifiers::SHIFT).unwrap();
harness
.send_key(KeyCode::Delete, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let thai_text = "ที่นี่";
harness.type_text(thai_text).unwrap();
harness.render().unwrap();
harness.assert_screen_contains(thai_text);
let (end_x, end_y) = harness.screen_cursor_position();
println!("Cursor at end: ({}, {})", end_x, end_y);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x1, _) = harness.screen_cursor_position();
println!("After 1st Left: x={}", x1);
assert_eq!(
x1,
end_x - 1,
"1st Left should skip entire Thai cluster 'นี่' (visual width 1)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x2, _) = harness.screen_cursor_position();
println!("After 2nd Left: x={}", x2);
assert_eq!(
x2,
end_x - 2,
"2nd Left should skip entire Thai cluster 'ที่' (visual width 1)"
);
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let (final_x, _) = harness.screen_cursor_position();
assert_eq!(
final_x, end_x,
"2 Right arrows should return cursor to end position"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
}
#[test]
#[ignore = "Settings search is a simple filter without cursor movement support"]
fn test_settings_search_grapheme_movement() {
let mut harness = EditorTestHarness::new(120, 40).unwrap();
harness.render().unwrap();
harness.open_settings().unwrap();
harness.assert_screen_contains("Settings");
harness
.send_key(KeyCode::Char('/'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let thai_text = "aที่b";
for c in thai_text.chars() {
harness
.send_key(KeyCode::Char(c), KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
harness.assert_screen_contains(thai_text);
let (end_x, end_y) = harness.screen_cursor_position();
println!("Cursor at end: ({}, {})", end_x, end_y);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x1, _) = harness.screen_cursor_position();
println!("After 1st Left: x={}", x1);
assert_eq!(
x1,
end_x - 1,
"1st Left should move back 1 column (past 'b')"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x2, _) = harness.screen_cursor_position();
println!("After 2nd Left: x={}", x2);
assert_eq!(
x2,
end_x - 2,
"2nd Left should skip entire Thai cluster (visual width 1)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let (x3, _) = harness.screen_cursor_position();
println!("After 3rd Left: x={}", x3);
assert_eq!(x3, end_x - 3, "3rd Left should move before 'a'");
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let (final_x, _) = harness.screen_cursor_position();
assert_eq!(
final_x, end_x,
"3 Right arrows should return cursor to end position"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
}
#[test]
fn test_main_editor_left_arrow_grapheme_movement() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.render().unwrap();
let thai_text = "ที่";
harness.type_text(thai_text).unwrap();
harness.render().unwrap();
let pos_end = harness.cursor_position();
assert_eq!(
pos_end, 9,
"After typing Thai cluster, cursor should be at byte 9"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_after_left = harness.cursor_position();
println!("After 1 Left: cursor at byte {}", pos_after_left);
assert_eq!(
pos_after_left, 0,
"Left arrow should move by entire grapheme cluster (from byte 9 to 0). \
If this fails with position 6, the bug is: left arrow moves by code point instead of grapheme"
);
}
#[test]
fn test_left_arrow_at_long_position_file_loaded() {
let temp_dir = TempDir::new().unwrap();
let thai_path = temp_dir.path().join("thai_long.txt");
let thai_content = "ที่นี่คือที่ติดตั้งระบบ";
std::fs::write(&thai_path, thai_content).unwrap();
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&thai_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos_end = harness.cursor_position();
println!("At end: cursor at byte {}", pos_end);
assert_eq!(
pos_end, 69,
"Cursor should be at byte 69 (end of Thai text)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos1 = harness.cursor_position();
println!("After 1st Left: cursor at byte {}", pos1);
assert_eq!(
pos1, 66,
"After Left from 69, should be at 66 (skipped บ which is 3 bytes)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos2 = harness.cursor_position();
println!("After 2nd Left: cursor at byte {}", pos2);
assert_eq!(
pos2, 63,
"After Left from 66, should be at 63 (skipped บ which is 3 bytes)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos3 = harness.cursor_position();
println!("After 3rd Left: cursor at byte {}", pos3);
assert_eq!(
pos3, 60,
"After Left from 63, should be at 60 (skipped ะ which is 3 bytes)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos4 = harness.cursor_position();
println!("After 4th Left: cursor at byte {}", pos4);
assert_eq!(
pos4, 57,
"After Left from 60, should be at 57 (skipped ร which is 3 bytes)"
);
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos5 = harness.cursor_position();
println!("After 5th Left: cursor at byte {}", pos5);
assert_eq!(pos5, 54, "After Left from 57, should be at 54 (skipped ง)");
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let pos6 = harness.cursor_position();
println!("After 6th Left: cursor at byte {}", pos6);
assert_eq!(
pos6, 45,
"After Left from 54, should be at 45 (skipped grapheme cluster ตั้ which is 9 bytes). \
If this is 51, the bug is present: left arrow fell back to code point movement."
);
}