test: the TUI cursor stays drawn beside a side panel and on the tile bar (improvise-gtn7.1)

Both fail on gh/main: the grid at column 4 with any side panel open, the
tile bar at width 42.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-24 18:25:40 -07:00
parent ae5cf40824
commit b945a7f881
2 changed files with 110 additions and 0 deletions
+78
View File
@@ -1120,3 +1120,81 @@ mod title_bar_tests {
);
}
}
#[cfg(test)]
mod content_tests {
use super::{draw, press};
use crate::command::key::{KeyCode, KeyEvent, KeyModifiers};
use crate::model::cell::{CellKey, CellValue};
use crate::ui::app::App;
use crate::view::Axis;
use crate::workbook::Workbook;
use ratatui::{Terminal, backend::TestBackend, style::Color};
const COLS: usize = 12;
/// One row, twelve columns, and a value per column that names it.
fn wide_grid() -> Workbook {
let mut wb = Workbook::new("T");
wb.add_category("Row").unwrap();
wb.add_category("Col").unwrap();
wb.add_item("Row", "R1").unwrap();
for i in 0..COLS {
let col = format!("Column_{i:02}");
wb.add_item("Col", &col).unwrap();
let key = CellKey::new(vec![("Row".into(), "R1".into()), ("Col".into(), col)]);
wb.model.set_cell(key, CellValue::Number((101 + i) as f64));
}
wb.active_view_mut().set_axis("Row", Axis::Row);
wb.active_view_mut().set_axis("Col", Axis::Column);
wb
}
/// The text the whole frame draws in the selected cell's style.
fn selected_text(app: &App) -> String {
let (w, h) = (app.term_width, app.term_height);
let mut terminal = Terminal::new(TestBackend::new(w, h)).expect("test backend");
terminal.draw(|f| draw(f, app)).expect("draw");
terminal
.backend()
.buffer()
.content()
.iter()
.filter(|c| c.bg == Color::Cyan && c.fg == Color::Black)
.map(|c| c.symbol())
.collect()
}
/// improvise-gtn7.1: the cursor went behind a side panel —
/// `context/test-evidence/tui.md`.
#[test]
fn moving_right_keeps_the_selected_cell_drawn_beside_any_side_panel() {
let panels: [(&str, fn(&mut App)); 4] = [
("no", |_| {}),
("formula", |a| a.view_state.formula_panel_open = true),
("category", |a| a.view_state.category_panel_open = true),
("view", |a| a.view_state.view_panel_open = true),
];
for (panel, open) in panels {
let mut app = App::new(wide_grid(), None);
(app.term_width, app.term_height) = (80, 24);
// The category panel is open at launch; close all three first.
let vs = &mut app.view_state;
(vs.formula_panel_open, vs.category_panel_open, vs.view_panel_open) =
(false, false, false);
open(&mut app);
for step in 1..COLS {
press(&mut app, KeyEvent::new(KeyCode::Right, KeyModifiers::NONE)).unwrap();
let col = app.cursor().selected.1;
assert_eq!(col, step, "setup: {panel} panel, the cursor moved");
let drawn = selected_text(&app);
assert!(
drawn.contains(&(101 + col).to_string()),
"{panel} panel: column {col} is selected (offset {}) but not \
drawn; the selection drew {drawn:?}",
app.cursor().col_offset
);
}
}
}
}
+32
View File
@@ -231,6 +231,38 @@ mod tests {
}
}
/// improvise-gtn7.1: the tile cursor could scroll off the bar —
/// `context/test-evidence/tui.md`.
#[test]
fn the_selected_tile_is_drawn_at_every_width_it_fits_in() {
let mut wb = Workbook::new("Test");
for i in 0..8 {
wb.add_category(&format!("Category_{i}")).unwrap();
}
let app = App::new(wb, None);
let cache = project::tile_bar_of(&app);
let names: Vec<&str> = cache.tiles.iter().map(|t| t.category.as_str()).collect();
assert!(names.len() > 8, "setup: more tiles than any tested width holds");
for width in 40u16..=200 {
for (sel, name) in names.iter().enumerate() {
let area = Rect::new(0, 0, width, 1);
let mut buf = Buffer::empty(area);
TileBar::new(&cache, &AppMode::TileSelect, sel).render(area, &mut buf);
let highlighted: String = buf
.content()
.iter()
.filter(|c| c.bg == Color::Cyan)
.map(|c| c.symbol())
.collect();
assert!(
highlighted.contains(&format!("[{name} ")),
"width {width}: tile {sel} ({name}) is selected but not drawn: {:?}",
buf_text(&buf)
);
}
}
}
/// The tiles themselves are what the bar is for, and deleting the hint must
/// not delete them. improvise-rr8n.
#[test]