use super::*;
use crate::base::Size;
use crate::input::{KeyEvent, MouseEvent};
use crate::render::Style;
fn region(anchor: (i32, i32), head: (i32, i32), clamp: Rect) -> Region {
Region {
anchor: Point::new(anchor.0, anchor.1),
head: Point::new(head.0, head.1),
clamp,
}
}
fn spans(r: &Region) -> Vec<Rect> {
let mut out = Vec::new();
r.row_spans(&mut out);
out
}
fn mouse(kind: MouseKind, button: MouseButton, x: i32, y: i32) -> Event {
Event::Mouse(MouseEvent::new(kind, button, Point::new(x, y), Mods::NONE))
}
fn viewport_clamp() -> Box<dyn FnMut(Point) -> Rect> {
Box::new(|_| Rect::new(0, 0, 80, 24))
}
#[test]
fn single_row_spans_min_to_max_inclusive() {
let clamp = Rect::new(0, 0, 20, 5);
let fwd = spans(®ion((2, 2), (5, 2), clamp));
let rev = spans(®ion((5, 2), (2, 2), clamp));
assert_eq!(fwd, vec![Rect::new(2, 2, 4, 1)]); assert_eq!(fwd, rev);
}
#[test]
fn multi_row_flows_first_full_last_within_clamp() {
let clamp = Rect::new(1, 1, 10, 5); let s = spans(®ion((4, 2), (6, 4), clamp));
assert_eq!(
s,
vec![
Rect::new(4, 2, 7, 1), Rect::new(1, 3, 10, 1), Rect::new(1, 4, 6, 1), ]
);
assert_eq!(s, spans(®ion((6, 4), (4, 2), clamp)));
}
#[test]
fn spans_never_escape_the_clamp() {
let clamp = Rect::new(2, 1, 5, 2); for span in spans(®ion((0, 0), (100, 100), clamp)) {
assert_eq!(
span.intersect(clamp),
span,
"span {span:?} escaped {clamp:?}"
);
}
}
#[test]
fn extract_trims_trailing_space_and_joins_rows() {
let mut s = Surface::new(Size::new(12, 3), crate::render::Cell::EMPTY);
s.draw_text(0, 0, "alpha", Style::new());
s.draw_text(0, 1, "beta", Style::new());
let r = region((0, 0), (11, 1), Rect::new(0, 0, 12, 3));
assert_eq!(extract_text(&s, &r), "alpha\nbeta");
}
#[test]
fn extract_keeps_interior_blank_rows() {
let mut s = Surface::new(Size::new(8, 3), crate::render::Cell::EMPTY);
s.draw_text(0, 0, "top", Style::new());
s.draw_text(0, 2, "bottom", Style::new());
let r = region((0, 0), (7, 2), Rect::new(0, 0, 8, 3));
assert_eq!(extract_text(&s, &r), "top\n\nbottom");
}
#[test]
fn extract_never_splits_wide_glyphs() {
let mut s = Surface::new(Size::new(12, 1), crate::render::Cell::EMPTY);
s.draw_text(0, 0, "ab 世界 cd", Style::new());
let clamp = Rect::new(0, 0, 12, 1);
let r = region((4, 0), (9, 0), clamp);
assert_eq!(extract_text(&s, &r), "世界 cd");
let r = region((0, 0), (5, 0), clamp);
assert_eq!(extract_text(&s, &r), "ab 世界");
}
#[test]
fn extract_mid_glyph_endpoints_cover_the_pair_symmetrically() {
let mut s = Surface::new(Size::new(6, 1), crate::render::Cell::EMPTY);
s.draw_text(0, 0, "x世y", Style::new());
let clamp = Rect::new(0, 0, 6, 1);
let r = region((2, 0), (2, 0), clamp);
assert_eq!(extract_text(&s, &r), "世");
}
#[test]
fn selection_claims_left_drag_only_wheel_and_buttons_pass() {
let sel = selection();
sel.set_enabled(true);
let mut clamp = viewport_clamp();
let wheel = mouse(MouseKind::WheelDown, MouseButton::None, 3, 3);
assert_eq!(sel.on_input(&wheel, &mut clamp), SelectionAct::Pass);
let rdown = mouse(MouseKind::Down, MouseButton::Right, 3, 3);
assert_eq!(sel.on_input(&rdown, &mut clamp), SelectionAct::Pass);
let down = mouse(MouseKind::Down, MouseButton::Left, 2, 1);
assert_eq!(sel.on_input(&down, &mut clamp), SelectionAct::Consumed);
assert!(!sel.is_active(), "a click alone never paints");
let drag = mouse(MouseKind::Drag, MouseButton::Left, 6, 2);
assert_eq!(sel.on_input(&drag, &mut clamp), SelectionAct::Consumed);
assert!(sel.is_active());
let up = mouse(MouseKind::Up, MouseButton::Left, 6, 2);
match sel.on_input(&up, &mut clamp) {
SelectionAct::Copy(_) => {}
other => panic!("release must copy, got {other:?}"),
}
assert!(!sel.is_active(), "the copy ends the gesture (0290)");
let enter = Event::Key(KeyEvent::plain(KeyCode::Enter));
let plain_c = Event::Key(KeyEvent::char('c'));
assert_eq!(sel.on_input(&enter, &mut clamp), SelectionAct::Pass);
assert_eq!(sel.on_input(&plain_c, &mut clamp), SelectionAct::Pass);
let down = mouse(MouseKind::Down, MouseButton::Left, 9, 9);
assert_eq!(sel.on_input(&down, &mut clamp), SelectionAct::Consumed);
assert!(!sel.is_active());
let up = mouse(MouseKind::Up, MouseButton::Left, 9, 9);
assert_eq!(
sel.on_input(&up, &mut clamp),
SelectionAct::Consumed,
"a click's paired release copies nothing"
);
sel.set_enabled(false);
let down = mouse(MouseKind::Down, MouseButton::Left, 2, 1);
assert_eq!(sel.on_input(&down, &mut clamp), SelectionAct::Pass);
}
#[test]
fn copy_keys_are_one_shot_and_exist_only_while_a_region_is_visible() {
let sel = selection();
sel.set_enabled(true);
let mut clamp = viewport_clamp();
let ctrl_c = Event::Key(KeyEvent::char('c').with_mods(Mods::CTRL));
let plain_c = Event::Key(KeyEvent::char('c'));
let enter = Event::Key(KeyEvent::plain(KeyCode::Enter));
let esc = Event::Key(KeyEvent::plain(KeyCode::Esc));
for ev in [&ctrl_c, &plain_c, &enter, &esc] {
assert_eq!(sel.on_input(ev, &mut clamp), SelectionAct::Pass);
}
let arm = |sel: &Selection, clamp: &mut Box<dyn FnMut(Point) -> Rect>| {
sel.on_input(&mouse(MouseKind::Down, MouseButton::Left, 1, 1), clamp);
sel.on_input(&mouse(MouseKind::Drag, MouseButton::Left, 5, 1), clamp);
assert!(sel.is_active());
};
for key in [&plain_c, &ctrl_c, &enter] {
arm(&sel, &mut clamp);
match sel.on_input(key, &mut clamp) {
SelectionAct::Copy(_) => {}
other => panic!("{key:?} must copy, got {other:?}"),
}
assert!(!sel.is_active(), "key-copy is one-shot: {key:?}");
assert_eq!(
sel.on_input(key, &mut clamp),
SelectionAct::Pass,
"the same key routes to the app right after the copy"
);
}
arm(&sel, &mut clamp);
let other = Event::Key(KeyEvent::char('x'));
assert_eq!(sel.on_input(&other, &mut clamp), SelectionAct::Pass);
assert_eq!(sel.on_input(&esc, &mut clamp), SelectionAct::Consumed);
assert!(!sel.is_active());
let up = mouse(MouseKind::Up, MouseButton::Left, 5, 1);
assert_eq!(sel.on_input(&up, &mut clamp), SelectionAct::Pass);
}
#[test]
fn drag_clamps_to_the_pane_resolved_at_anchor() {
let sel = selection();
sel.set_enabled(true);
let pane = Rect::new(2, 1, 6, 2);
let mut clamp: Box<dyn FnMut(Point) -> Rect> = Box::new(move |_| pane);
sel.on_input(&mouse(MouseKind::Down, MouseButton::Left, 3, 1), &mut clamp);
sel.on_input(
&mouse(MouseKind::Drag, MouseButton::Left, 50, 20),
&mut clamp,
);
let region = sel.state.borrow().region.expect("region");
let mut out = Vec::new();
region.row_spans(&mut out);
for span in out {
assert_eq!(span.intersect(pane), span, "span {span:?} escaped the pane");
}
}
#[test]
fn disabling_mid_selection_clears_and_marks_repair() {
let sel = selection();
sel.set_enabled(true);
let mut clamp = viewport_clamp();
sel.on_input(&mouse(MouseKind::Down, MouseButton::Left, 1, 1), &mut clamp);
sel.on_input(&mouse(MouseKind::Drag, MouseButton::Left, 4, 1), &mut clamp);
assert!(sel.is_active());
sel.set_enabled(false);
assert!(!sel.is_active());
let mut surf = Surface::new(Size::new(10, 3), crate::render::Cell::EMPTY);
let mut drained = Vec::new();
surf.take_damage(&mut drained); sel.add_flatten_damage(&mut surf);
let mut out = Vec::new();
surf.take_damage(&mut out);
assert!(out.is_empty(), "nothing was ever painted: {out:?}");
}
#[test]
fn paint_patches_inks_keeps_glyphs_and_records_rects() {
let sel = selection();
sel.set_enabled(true);
let mut clamp = viewport_clamp();
sel.on_input(&mouse(MouseKind::Down, MouseButton::Left, 0, 0), &mut clamp);
sel.on_input(&mouse(MouseKind::Drag, MouseButton::Left, 4, 0), &mut clamp);
let mut frame = Surface::new(Size::new(10, 2), crate::render::Cell::EMPTY);
frame.draw_text(0, 0, "hi 世x", Style::new());
let fg = Rgba::rgb(1, 2, 3);
let bg = Rgba::rgb(9, 8, 7);
sel.paint_into(&mut frame, fg, bg);
let h = frame.get(0, 0).copied().unwrap();
assert_eq!(frame.glyph_str(&h), "h");
assert_eq!((h.fg, h.bg), (fg, bg));
let wide = frame.get(3, 0).copied().unwrap();
assert_eq!(frame.glyph_str(&wide), "世");
assert_eq!((wide.fg, wide.bg), (fg, bg));
let cont = frame.get(4, 0).copied().unwrap();
assert!(cont.is_continuation());
assert_eq!(cont.bg, bg, "continuation carries the leader's inks");
sel.clear();
let mut surf = Surface::new(Size::new(10, 2), crate::render::Cell::EMPTY);
let mut drained = Vec::new();
surf.take_damage(&mut drained);
sel.add_flatten_damage(&mut surf);
let mut out = Vec::new();
surf.take_damage(&mut out);
assert_eq!(out, vec![Rect::new(0, 0, 5, 1)]);
}
#[test]
fn flatten_damage_covers_old_and_new_regions() {
let sel = selection();
sel.set_enabled(true);
let mut clamp = viewport_clamp();
sel.on_input(&mouse(MouseKind::Down, MouseButton::Left, 0, 0), &mut clamp);
sel.on_input(&mouse(MouseKind::Drag, MouseButton::Left, 3, 0), &mut clamp);
let mut frame = Surface::new(Size::new(10, 2), crate::render::Cell::EMPTY);
frame.draw_text(0, 0, "abcdefgh", Style::new());
let mut scratch = Vec::new();
frame.take_damage(&mut scratch);
sel.add_flatten_damage(&mut frame);
let mut first = Vec::new();
frame.take_damage(&mut first);
assert_eq!(first, vec![Rect::new(0, 0, 4, 1)]);
sel.paint_into(&mut frame, Rgba::rgb(1, 1, 1), Rgba::rgb(2, 2, 2));
sel.on_input(&mouse(MouseKind::Drag, MouseButton::Left, 6, 1), &mut clamp);
sel.add_flatten_damage(&mut frame);
let mut second = Vec::new();
frame.take_damage(&mut second);
let covers = |x: i32, y: i32| second.iter().any(|r| r.contains(Point::new(x, y)));
assert!(covers(0, 0), "old painted cells recompose: {second:?}");
assert!(covers(6, 1), "new head row recomposes: {second:?}");
sel.paint_into(&mut frame, Rgba::rgb(1, 1, 1), Rgba::rgb(2, 2, 2));
sel.add_flatten_damage(&mut frame);
let mut third = Vec::new();
frame.take_damage(&mut third);
assert!(third.is_empty(), "steady selection costs zero damage");
}
#[test]
fn pane_walk_prefers_clipping_or_padded_ancestors_else_root() {
use crate::layout::Edges;
use crate::ui::{text, Element};
let mut app = super::super::App::new(Size::new(40, 12));
app.mount(|_cx| {
Element::new()
.style(crate::layout::Style::row())
.child(
Element::new()
.style(
crate::layout::Style::column()
.width(crate::layout::Dimension::Cells(20))
.clip(),
)
.child(text("left one"))
.child(text("left two"))
.build(),
)
.child(
Element::new()
.style(
crate::layout::Style::column()
.grow(1.0)
.padding(Edges::all(1)),
)
.child(text("right"))
.build(),
)
.build()
})
.unwrap();
app.pump();
app.tree().layout();
let left = app.tree().pane_rect_at(Point::new(3, 1)).unwrap();
assert_eq!(left, Rect::new(0, 0, 20, 12));
let right = app.tree().pane_rect_at(Point::new(25, 3)).unwrap();
assert_eq!(right, Rect::new(21, 1, 18, 10));
let gutter = app.tree().pane_rect_at(Point::new(20, 0)).unwrap();
assert_eq!(gutter, Rect::new(0, 0, 40, 12));
assert_eq!(app.tree().pane_rect_at(Point::new(100, 100)), None);
}