use escriba_buffer::BufferSet;
use escriba_core::{Action, Mode};
use escriba_keymap::Key;
use escriba_runtime::EditorState;
fn editor() -> EditorState {
let mut bufs = BufferSet::new();
let id = bufs.scratch("hello world\n");
EditorState::new_with_buffer(bufs, id)
}
fn press_command(st: &mut EditorState, name: &str) {
let key = Key::Char('\u{1}'); st.keymap.bind(
Mode::Normal,
key.clone(),
Action::Command {
name: name.to_string(),
args: Vec::new(),
},
"test binding",
);
st.on_key(&key);
}
#[test]
fn an_unregistered_command_is_reported() {
let mut st = editor();
press_command(&mut st, "totally.not.a.command");
let msg = st
.messages
.last()
.expect("a dead command must say something");
assert!(
msg.contains("totally.not.a.command"),
"the message must name what failed: {msg:?}",
);
}
#[test]
fn a_registered_but_unimplemented_action_is_reported() {
let mut st = editor();
st.commands.register(escriba_command::Command::action(
"pick",
"Pick a file",
"picker.files",
));
press_command(&mut st, "pick");
let msg = st
.messages
.last()
.expect("an inert action must say something");
assert!(
msg.contains("picker.files"),
"the message must name the unimplemented action: {msg:?}",
);
assert!(
msg.contains("not implemented"),
"and must distinguish 'not built yet' from 'no such command': {msg:?}",
);
}
#[test]
fn a_failed_command_is_never_fatal() {
let mut st = editor();
press_command(&mut st, "nope.not.here");
assert!(
!st.quit_requested,
"a dead command must not exit the editor"
);
assert_eq!(
st.buffers.get(st.active).map(|b| b.to_string()),
Some("hello world\n".to_string()),
"the buffer must be untouched",
);
st.on_key(&Key::Char('j'));
assert_eq!(st.cursor().line, 1, "input still lands after a failure");
}
#[test]
fn a_working_command_adds_no_verdict_noise() {
let mut st = editor();
let before = st.messages.len();
press_command(&mut st, "undo"); assert_eq!(
st.messages.len().saturating_sub(before),
0,
"a command that worked must not report: {:?}",
st.messages,
);
}
#[test]
fn a_command_whose_job_is_to_report_still_speaks() {
let mut st = editor();
press_command(&mut st, "buffer-info");
let msg = st.messages.last().expect("buffer-info reports");
assert!(msg.contains("line(s)"), "{msg:?}");
}
#[test]
fn a_failure_forces_a_repaint() {
let mut st = editor();
let gen_before = st.edit_gen();
press_command(&mut st, "nope.not.here");
assert_ne!(
gen_before,
st.edit_gen(),
"a reported failure must invalidate the cached frame",
);
}
#[test]
fn the_message_reaches_the_shared_status_model() {
let mut st = editor();
press_command(&mut st, "nope.not.here");
let model = st.status_model();
assert!(
model.message.is_some_and(|m| m.contains("nope.not.here")),
"the failure must reach the status model both faces render",
);
}
#[test]
fn a_typo_and_an_unbuilt_capability_read_differently() {
let mut st = editor();
press_command(&mut st, "flurb");
let typo = st.messages.last().cloned().expect("a message");
assert!(
typo.contains("not found"),
"a bare name the operator mistyped is not-found: {typo:?}",
);
let mut st = editor();
press_command(&mut st, "picker.files");
let unbuilt = st.messages.last().cloned().expect("a message");
assert!(
unbuilt.contains("not implemented"),
"a declared-but-unbuilt action must not read as a typo: {unbuilt:?}",
);
assert!(
!unbuilt.contains("not found"),
"and must not blame the operator: {unbuilt:?}",
);
}