pmacs/tests/listview_acceptance.rs

139 lines
4.6 KiB
Rust

//! List-panel acceptance (Arc 1b phase 1) --- the `pmacs.listview`
//! module end-to-end through `dispatch_key`: open/navigate/visit,
//! `q` restore, the Q#P3 read-only intercept, the Q#P6 round-trip
//! gate (`dispatch_idle` false while a panel is focused), and
//! refresh. The references panel itself needs a live LSP and is
//! validated manually / via the m4 harness; these tests drive the
//! substrate hermetically.
//!
//! Framing: docs/lsp-panels-framing.md.
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
use pmacs::editor::EditorState;
use pmacs::protocol::FrontendId;
fn key(code: KeyCode, mods: KeyModifiers) -> KeyEvent {
KeyEvent {
code,
modifiers: mods,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
fn press(s: &mut EditorState, code: KeyCode) {
s.dispatch_key(FrontendId::LOCAL, key(code, KeyModifiers::NONE));
}
/// Open a three-row test panel whose visits record into `_G.VISITED`.
fn open_test_panel(s: &mut EditorState) {
s.lua_host
.lua()
.load(
r#"
_G.VISITED = nil
pmacs.listview.open {
name = "*test-panel*",
header = "3 items RET visit q quit",
rows = {
{ text = "alpha", item = "A" },
{ text = "beta", item = "B" },
{ text = "gamma", item = "C" },
},
on_visit = function(item) _G.VISITED = item end,
on_refresh = function()
return { { text = "delta", item = "D" } }
end,
}
"#,
)
.exec()
.expect("open test panel");
}
/// `(active buffer name, buffer text, cursor line, visited)` probed
/// through the Lua surface.
fn probe(s: &EditorState) -> (String, String, i64, Option<String>) {
s.lua_host
.lua()
.load(
r"
local b = pmacs.window.buffer()
local d = pmacs.describe.buffer(b)
return d.name, b:slice(0, b:len()), pmacs.editor.cursor_line(), _G.VISITED
",
)
.eval()
.expect("probe panel state")
}
#[test]
fn open_seats_cursor_and_ret_visits_the_row() {
let mut s = EditorState::new();
open_test_panel(&mut s);
let (name, text, line, _) = probe(&s);
assert_eq!(name, "*test-panel*");
assert!(text.starts_with("3 items"), "header renders first");
assert_eq!(line, 1, "the cursor opens on the first data row");
press(&mut s, KeyCode::Char('n')); // buffer-local: cursor.down
press(&mut s, KeyCode::Enter);
let (_, _, _, visited) = probe(&s);
assert_eq!(visited.as_deref(), Some("B"), "RET visits the second row");
}
#[test]
fn header_row_is_not_visitable() {
let mut s = EditorState::new();
open_test_panel(&mut s);
press(&mut s, KeyCode::Char('p')); // up onto the header
press(&mut s, KeyCode::Enter);
let (_, _, _, visited) = probe(&s);
assert_eq!(visited, None, "the header maps to no item");
}
#[test]
fn q_restores_the_previous_buffer() {
let mut s = EditorState::new();
open_test_panel(&mut s);
press(&mut s, KeyCode::Char('q'));
let (name, _, _, _) = probe(&s);
assert_eq!(name, "*scratch*", "q returns to the buffer we came from");
}
#[test]
fn panel_rejects_typing() {
let mut s = EditorState::new();
open_test_panel(&mut s);
let (_, before, _, _) = probe(&s);
press(&mut s, KeyCode::Char('z')); // unbound printable → self-insert → intercept rejects
let (_, after, _, _) = probe(&s);
assert_eq!(before, after, "the read-only intercept rejects self-insert");
}
#[test]
fn dispatch_idle_is_false_while_a_panel_is_focused() {
// Q#P6: while the panel is the active buffer, semantic frontends
// must round-trip every key (RET = visit, not an optimistic \n).
let mut s = EditorState::new();
assert!(s.dispatch_idle(), "scratch buffer: idle");
open_test_panel(&mut s);
assert!(!s.dispatch_idle(), "panel focused: keys must round-trip");
press(&mut s, KeyCode::Char('q'));
assert!(s.dispatch_idle(), "restored buffer: idle again");
}
#[test]
fn refresh_reruns_the_source_and_reseats() {
let mut s = EditorState::new();
open_test_panel(&mut s);
press(&mut s, KeyCode::Char('g'));
let (_, text, line, _) = probe(&s);
assert!(text.contains("delta"), "g re-renders from on_refresh");
assert!(!text.contains("alpha"), "old rows are gone");
assert_eq!(line, 1, "cursor re-seats on a data row after refresh");
press(&mut s, KeyCode::Enter);
let (_, _, _, visited) = probe(&s);
assert_eq!(visited.as_deref(), Some("D"), "the refreshed row visits");
}