use super::*;
use crate::options::SkimOptionsBuilder;
use crate::tui::options::PreviewLayout;
fn area() -> Rect {
Rect::new(0, 0, 80, 24)
}
fn assert_full_width(rect: Rect, area: Rect, label: &str) {
assert_eq!(rect.x, area.x, "{label}: x");
assert_eq!(rect.width, area.width, "{label}: width");
}
fn assert_vertically_adjacent(a: Rect, b: Rect, label: &str) {
assert_eq!(a.y + a.height, b.y, "{label}: b should start right after a");
}
fn assert_horizontally_adjacent(a: Rect, b: Rect, label: &str) {
assert_eq!(a.x + a.width, b.x, "{label}: b should start right after a");
}
fn compute(options: &SkimOptions) -> AppLayout {
AppLayout::compute(area(), options, 0)
}
fn compute_with_header_height(options: &SkimOptions, header_height: u16) -> AppLayout {
AppLayout::compute(area(), options, header_height)
}
fn opts() -> SkimOptionsBuilder {
SkimOptionsBuilder::default()
}
#[test]
fn default_no_preview_no_header() {
let options = opts().build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.height, 2);
assert_eq!(layout.list_area.height, 24 - 2);
assert!(layout.header_area.is_none());
assert!(layout.preview_area.is_none());
assert_full_width(layout.list_area, area(), "list");
assert_full_width(layout.input_area, area(), "input");
assert_vertically_adjacent(layout.list_area, layout.input_area, "list→input");
}
#[test]
fn default_inline_info_no_header() {
let options = opts().inline_info(true).build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.height, 1);
assert_eq!(layout.list_area.height, 23);
}
#[test]
fn default_hidden_info_no_header() {
let options = opts().no_info(true).build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.height, 1);
assert_eq!(layout.list_area.height, 23);
}
#[test]
fn default_with_header() {
let options = opts().header("My Header").build().unwrap();
let layout = compute_with_header_height(&options, 1);
assert_eq!(layout.list_area.height, 21);
assert_eq!(layout.header_area.unwrap().height, 1);
assert_eq!(layout.input_area.height, 2);
let h = layout.header_area.unwrap();
assert_vertically_adjacent(layout.list_area, h, "list→header");
assert_vertically_adjacent(h, layout.input_area, "header→input");
}
#[test]
fn default_with_multiline_header() {
let options = opts().header("line1\nline2\nline3").build().unwrap();
let layout = compute_with_header_height(&options, 3);
assert_eq!(layout.list_area.height, 19);
assert_eq!(layout.header_area.unwrap().height, 3);
}
#[test]
fn default_with_header_lines() {
let options = opts().header_lines(2usize).build().unwrap();
let layout = compute_with_header_height(&options, 2);
assert_eq!(layout.header_area.unwrap().height, 2);
assert_eq!(layout.list_area.height, 24 - 2 - 2);
}
#[test]
fn template_apply_different_areas() {
let options = opts().build().unwrap();
let template = LayoutTemplate::from_options(&options, 0);
let small = template.apply(Rect::new(0, 0, 40, 12));
let large = template.apply(Rect::new(0, 0, 160, 48));
assert_eq!(small.input_area.height, 2);
assert_eq!(small.list_area.height, 10);
assert_eq!(large.input_area.height, 2);
assert_eq!(large.list_area.height, 46);
}
#[test]
fn reverse_no_preview_no_header() {
let options = opts().layout(TuiLayout::Reverse).build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.y, 0);
assert_eq!(layout.input_area.height, 2);
assert_eq!(layout.list_area.y, 2);
assert_eq!(layout.list_area.height, 22);
assert_vertically_adjacent(layout.input_area, layout.list_area, "input→list");
}
#[test]
fn reverse_with_header() {
let options = opts().layout(TuiLayout::Reverse).header("hdr").build().unwrap();
let layout = compute_with_header_height(&options, 2);
assert_eq!(layout.list_area.height, 20);
let h = layout.header_area.unwrap();
assert_eq!(h.height, 2);
assert_vertically_adjacent(layout.input_area, h, "input→header");
assert_vertically_adjacent(h, layout.list_area, "header→list");
}
#[test]
fn reverse_list_no_preview_no_header() {
let options = opts().layout(TuiLayout::ReverseList).build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.height, 2);
assert_eq!(layout.list_area.height, 22);
assert_vertically_adjacent(layout.list_area, layout.input_area, "list→input");
}
#[test]
fn reverse_list_with_header() {
let options = opts().layout(TuiLayout::ReverseList).header("hdr").build().unwrap();
let layout = compute_with_header_height(&options, 1);
assert_eq!(layout.list_area.height, 21);
let h = layout.header_area.unwrap();
assert_eq!(h.height, 1);
assert_vertically_adjacent(layout.list_area, h, "list→header");
assert_vertically_adjacent(h, layout.input_area, "header→input");
}
#[test]
fn default_preview_right_50_percent() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("right:50%"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.width, 40);
assert_eq!(preview.x, 40);
assert_eq!(layout.list_area.width, 40);
assert_eq!(layout.input_area.width, 40);
assert_horizontally_adjacent(layout.list_area, preview, "list→preview");
}
#[test]
fn default_preview_left_30_percent() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("left:30%"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.width, 24);
assert_eq!(preview.x, 0);
assert_eq!(layout.list_area.x, 24);
assert_horizontally_adjacent(preview, layout.list_area, "preview→list");
}
#[test]
fn default_preview_right_fixed_20() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("right:20"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.width, 20);
assert_eq!(layout.list_area.width, 60);
}
#[test]
fn reverse_preview_left() {
let options = opts()
.layout(TuiLayout::Reverse)
.preview("cat {}")
.preview_window(PreviewLayout::from("left:40%"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.width, 32);
assert_eq!(layout.input_area.y, 0);
assert_eq!(layout.input_area.x, 32);
}
#[test]
fn default_preview_up_50_percent() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("up:50%"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.height, 12);
assert_eq!(preview.y, 0);
assert_eq!(layout.list_area.y, 12);
assert_eq!(layout.list_area.height, 10);
assert_eq!(layout.input_area.y, 22);
}
#[test]
fn default_preview_down_50_percent() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("down:50%"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.height, 12);
assert_eq!(layout.list_area.y, 0);
assert_eq!(layout.list_area.height, 10);
assert_eq!(layout.input_area.y, 10);
assert_eq!(preview.y, 12);
assert_vertically_adjacent(layout.input_area, preview, "input→preview");
}
#[test]
fn default_preview_up_fixed_8() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("up:8"))
.build()
.unwrap();
let layout = compute(&options);
let preview = layout.preview_area.unwrap();
assert_eq!(preview.height, 8);
assert_eq!(preview.y, 0);
assert_eq!(layout.list_area.height, 14);
assert_full_width(preview, area(), "preview");
}
#[test]
fn preview_hidden_produces_no_preview_area() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("right:50%:hidden"))
.build()
.unwrap();
let layout = compute(&options);
assert!(layout.preview_area.is_none());
assert_eq!(layout.list_area.width, 80);
}
#[test]
fn no_preview_command_produces_no_preview_area() {
let options = opts().preview_window(PreviewLayout::from("right:50%")).build().unwrap();
let layout = compute(&options);
assert!(layout.preview_area.is_none());
assert_eq!(layout.list_area.width, 80);
}
#[test]
fn default_with_borders_no_header() {
let options = opts().border(crate::tui::BorderType::Plain).build().unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.height, 3);
assert_eq!(layout.list_area.height, 21);
assert!(layout.header_area.is_none());
}
#[test]
fn default_with_borders_and_header() {
let options = opts()
.border(crate::tui::BorderType::Plain)
.header("hdr")
.build()
.unwrap();
let layout = compute_with_header_height(&options, 2);
assert_eq!(layout.input_area.height, 3);
let h = layout.header_area.unwrap();
assert_eq!(h.height, 4);
assert_eq!(layout.list_area.height, 24 - 3 - 4);
}
#[test]
fn reverse_with_borders() {
let options = opts()
.layout(TuiLayout::Reverse)
.border(crate::tui::BorderType::Plain)
.build()
.unwrap();
let layout = compute(&options);
assert_eq!(layout.input_area.y, 0);
assert_eq!(layout.input_area.height, 3);
assert_eq!(layout.list_area.y, 3);
assert_eq!(layout.list_area.height, 21);
}
#[test]
fn all_areas_non_overlapping_default() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("right:40%"))
.header("hdr")
.build()
.unwrap();
let layout = compute_with_header_height(&options, 2);
let preview = layout.preview_area.unwrap();
let header = layout.header_area.unwrap();
assert!(
layout.list_area.x + layout.list_area.width <= preview.x || preview.x + preview.width <= layout.list_area.x,
"list and preview overlap"
);
let rects = [layout.list_area, header, layout.input_area];
for i in 0..rects.len() {
for j in (i + 1)..rects.len() {
let a = rects[i];
let b = rects[j];
let vertically_disjoint = a.y + a.height <= b.y || b.y + b.height <= a.y;
assert!(vertically_disjoint, "rects[{i}] and rects[{j}] overlap vertically");
}
}
}
#[test]
fn all_areas_non_overlapping_reverse() {
let options = opts()
.layout(TuiLayout::Reverse)
.inline_info(true)
.border(crate::tui::BorderType::Plain)
.preview("cat {}")
.preview_window(PreviewLayout::from("left:25"))
.header("hdr")
.build()
.unwrap();
let layout = compute_with_header_height(&options, 1);
let preview = layout.preview_area.unwrap();
let header = layout.header_area.unwrap();
assert_eq!(preview.x, 0);
assert_eq!(preview.width, 25);
assert_eq!(layout.list_area.x, 25);
assert_vertically_adjacent(layout.input_area, header, "input→header");
assert_vertically_adjacent(header, layout.list_area, "header→list");
}
#[test]
fn total_height_is_area_height_default() {
let options = opts().header("hdr").build().unwrap();
let layout = compute_with_header_height(&options, 3);
let total = layout.list_area.height + layout.header_area.unwrap().height + layout.input_area.height;
assert_eq!(total, area().height);
}
#[test]
fn total_width_is_area_width_with_right_preview() {
let options = opts()
.preview("cat {}")
.preview_window(PreviewLayout::from("right:50%"))
.build()
.unwrap();
let layout = compute(&options);
let total = layout.list_area.width + layout.preview_area.unwrap().width;
assert_eq!(total, area().width);
}
#[test]
fn total_height_is_area_height_with_down_preview() {
let options = opts()
.inline_info(true)
.preview("cat {}")
.preview_window(PreviewLayout::from("down:6"))
.build()
.unwrap();
let layout = compute(&options);
let total = layout.preview_area.unwrap().height + layout.list_area.height + layout.input_area.height;
assert_eq!(total, area().height);
}
#[test]
fn reverse_list_with_header_and_preview_right() {
let options = opts()
.layout(TuiLayout::ReverseList)
.preview("cat {}")
.preview_window(PreviewLayout::from("right:30%"))
.header("hdr")
.build()
.unwrap();
let layout = compute_with_header_height(&options, 1);
let preview = layout.preview_area.unwrap();
let header = layout.header_area.unwrap();
assert!(preview.x > 0);
assert_vertically_adjacent(layout.list_area, header, "list→header");
assert_vertically_adjacent(header, layout.input_area, "header→input");
assert_eq!(layout.list_area.x, layout.input_area.x);
}
#[test]
fn very_small_area() {
let tiny = Rect::new(0, 0, 20, 5);
let options = opts().header("hdr").build().unwrap();
let layout = AppLayout::compute(tiny, &options, 1);
assert_eq!(layout.list_area.width, 20);
}