use escriba_buffer::BufferSet;
use escriba_core::Mode;
use escriba_keymap::Key;
use escriba_runtime::EditorState;
fn on_file(contents: &str) -> (EditorState, tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("note.txt");
std::fs::write(&path, contents).expect("seed the file");
let mut bufs = BufferSet::new();
let id = bufs.open(&path).expect("open");
(EditorState::new_with_buffer(bufs, id), dir, path)
}
fn scratch(contents: &str) -> EditorState {
let mut bufs = BufferSet::new();
let id = bufs.scratch(contents);
EditorState::new_with_buffer(bufs, id)
}
fn ex(st: &mut EditorState, line: &str) {
st.on_key(&Key::Char(':'));
for c in line.chars() {
st.on_key(&Key::Char(c));
}
st.on_key(&Key::Enter);
if st.modal.mode() == Mode::Command {
st.on_key(&Key::Esc);
}
}
fn type_into(st: &mut EditorState, text: &str) {
st.on_key(&Key::Char('i'));
for c in text.chars() {
st.on_key(&Key::Char(c));
}
st.on_key(&Key::Esc);
}
fn said(st: &EditorState, needle: &str) -> bool {
st.messages.iter().any(|m| m.contains(needle))
}
#[test]
fn every_spelling_of_write_and_quit_writes_and_quits() {
for spelling in ["wq", "wq!", "wqa", "wqall", "xa", "xall", "x", "xit", "exi", "exit"] {
let (mut st, _dir, path) = on_file("before\n");
type_into(&mut st, "AFTER ");
ex(&mut st, spelling);
assert!(
st.quit_requested,
"`:{spelling}` must ask to exit — it did not",
);
let on_disk = std::fs::read_to_string(&path).expect("read back");
assert!(
on_disk.contains("AFTER "),
"`:{spelling}` must write the buffer first; disk says {on_disk:?}",
);
}
}
#[test]
fn exit_write_leaves_an_unmodified_file_alone() {
let (mut st, _dir, path) = on_file("untouched\n");
let before = std::fs::metadata(&path).and_then(|m| m.modified()).ok();
ex(&mut st, "x");
assert!(st.quit_requested, "`:x` exits whether or not it wrote");
let after = std::fs::metadata(&path).and_then(|m| m.modified()).ok();
assert_eq!(before, after, "`:x` on an unmodified buffer must not write");
}
#[test]
fn quit_refuses_unsaved_work_and_the_bang_overrides() {
for (plain, forced) in [("q", "q!"), ("quit", "quit!"), ("qa", "qa!"), ("qall", "qall!")] {
let mut st = scratch("");
type_into(&mut st, "unsaved");
ex(&mut st, plain);
assert!(
!st.quit_requested,
"`:{plain}` must refuse while a buffer is modified",
);
assert!(
said(&st, "E37"),
"the refusal must SAY so — a silent one reads as a dropped key: {:?}",
st.messages,
);
ex(&mut st, forced);
assert!(st.quit_requested, "`:{forced}` must exit anyway");
}
}
#[test]
fn quit_leaves_immediately_when_there_is_nothing_to_lose() {
for spelling in ["q", "qu", "quit", "qa", "qall", "quita", "quitall"] {
let mut st = scratch("clean\n");
ex(&mut st, spelling);
assert!(st.quit_requested, "`:{spelling}` must exit a clean editor");
}
}
#[test]
fn write_quit_on_an_unnamed_buffer_refuses_rather_than_losing_it() {
let mut st = scratch("");
type_into(&mut st, "nowhere to put this");
ex(&mut st, "wq");
assert!(
!st.quit_requested,
"`:wq` with no file name must not exit — the write it promised is impossible",
);
assert!(said(&st, "E32"), "and it must say why: {:?}", st.messages);
}
#[test]
fn the_write_only_spellings_write_and_stay() {
for spelling in ["w", "wr", "writ", "write", "wa", "wall"] {
let (mut st, _dir, path) = on_file("before\n");
type_into(&mut st, "AFTER ");
ex(&mut st, spelling);
assert!(!st.quit_requested, "`:{spelling}` must not exit");
let on_disk = std::fs::read_to_string(&path).expect("read back");
assert!(
on_disk.contains("AFTER "),
"`:{spelling}` must write; disk says {on_disk:?}",
);
}
}
#[test]
fn a_registry_name_still_reaches_its_command() {
let mut st = scratch("one\n");
ex(&mut st, "buffer-info");
assert!(
said(&st, "buffer"),
"a non-vim command name must still dispatch: {:?}",
st.messages,
);
}