use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use typ_buffer::Position;
use typ_core::{Action, Direction, Motion, Panel, RenderContext, ThemeColors};
use typ_panel_editor::EditorPanel;
fn mv(motion: Motion) -> Action {
Action::Move {
motion,
extend: false,
}
}
fn del(direction: Direction) -> Action {
Action::Delete {
direction,
by_word: false,
}
}
fn render(p: &mut EditorPanel, area: Rect) {
let theme = ThemeColors::default();
let ctx = RenderContext {
theme: &theme,
is_focused: true,
panel_index: 0,
terminal_width: area.width,
terminal_height: area.height,
};
let mut buf = Buffer::empty(area);
p.render(area, &mut buf, &ctx);
}
#[test]
fn enter_splits_the_line_and_moves_to_its_start() {
let mut p = EditorPanel::from_str("ab\n");
p.apply_action(mv(Motion::Right));
p.apply_action(Action::InsertNewline);
assert_eq!(p.cursor(), Position { line: 1, col: 0 });
assert_eq!(p.line_text(0), "a");
assert_eq!(p.line_text(1), "b");
}
#[test]
fn home_and_end_jump_to_the_line_bounds() {
let mut p = EditorPanel::from_str("hello\n");
p.apply_action(mv(Motion::LineEnd));
assert_eq!(p.cursor(), Position { line: 0, col: 5 });
p.apply_action(mv(Motion::LineStart));
assert_eq!(p.cursor(), Position { line: 0, col: 0 });
}
#[test]
fn delete_removes_the_grapheme_under_the_cursor() {
let mut p = EditorPanel::from_str("abc\n");
p.apply_action(del(Direction::Forward));
assert_eq!(p.line_text(0), "bc");
assert_eq!(p.cursor(), Position { line: 0, col: 0 });
}
#[test]
fn backspace_at_column_zero_joins_the_previous_line() {
let mut p = EditorPanel::from_str("ab\ncd\n");
p.apply_action(mv(Motion::Down));
p.apply_action(del(Direction::Backward));
assert_eq!(p.line_text(0), "abcd");
assert_eq!(p.cursor(), Position { line: 0, col: 2 });
}
#[test]
fn page_down_moves_a_screen_at_a_time() {
let text = (0..100).map(|i| format!("line {i}\n")).collect::<String>();
let mut p = EditorPanel::from_str(&text);
render(&mut p, Rect::new(0, 0, 40, 12)); p.apply_action(mv(Motion::PageDown));
assert_eq!(p.cursor().line, 10);
p.apply_action(mv(Motion::PageUp));
assert_eq!(p.cursor().line, 0);
}
#[test]
fn undo_and_redo_round_trip_an_edit() {
let mut p = EditorPanel::from_str("a\n");
p.apply_action(mv(Motion::LineEnd));
p.apply_action(Action::InsertChar('b'));
assert_eq!(p.line_text(0), "ab");
p.apply_action(Action::Undo);
assert_eq!(p.line_text(0), "a");
p.apply_action(Action::Redo);
assert_eq!(p.line_text(0), "ab");
}
#[test]
fn the_cursor_sits_inside_the_border_at_the_text_position() {
let mut p = EditorPanel::from_str("hello\n");
let area = Rect::new(0, 0, 40, 10);
render(&mut p, area);
p.apply_action(mv(Motion::Right));
p.apply_action(mv(Motion::Right));
assert_eq!(p.cursor_position(area), Some((3, 1)));
}
#[test]
fn the_cursor_accounts_for_wide_characters() {
let mut p = EditorPanel::from_str("日本語\n");
let area = Rect::new(0, 0, 40, 10);
render(&mut p, area);
p.apply_action(mv(Motion::Right));
assert_eq!(p.cursor_position(area), Some((3, 1)));
}
#[test]
fn the_cursor_follows_the_viewport_when_scrolled() {
let text = (0..100).map(|i| format!("line {i}\n")).collect::<String>();
let mut p = EditorPanel::from_str(&text);
let area = Rect::new(0, 0, 40, 10);
render(&mut p, area);
p.handle_scroll(5, area);
assert_eq!(p.cursor_position(area), None);
}