refactor: derive canonical commands from core actions and centralize aliases
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use vamp::editor::{Editor, Feature, Input};
|
||||
|
||||
static NEXT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct Fixture(PathBuf);
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"vamp-commands-{}-{}",
|
||||
std::process::id(),
|
||||
NEXT.fetch_add(1, Ordering::Relaxed),
|
||||
));
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
Self(root)
|
||||
}
|
||||
|
||||
fn editor(&self, name: &str, text: &str) -> Editor {
|
||||
let path = self.0.join(name);
|
||||
fs::write(&path, text).unwrap();
|
||||
Editor::open(Some(path), vec!["text".into()]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(editor: &mut Editor, text: &str) {
|
||||
for input in [Input::Char('i'), Input::Paste(text.into()), Input::Escape] {
|
||||
editor.handle(input);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_and_w_preserve_spaced_path_arguments_and_write_current_contents() {
|
||||
for command in ["save", "w"] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor("original file.txt", "original\n");
|
||||
insert(&mut editor, "changed ");
|
||||
let destination = fixture.0.join("saved file with spaces.txt");
|
||||
|
||||
editor.execute(&format!(":{command} {}", destination.display()));
|
||||
|
||||
assert_eq!(editor.path(), Some(destination.as_path()), "{command}");
|
||||
assert_eq!(
|
||||
fs::read_to_string(&destination).unwrap(),
|
||||
"changed original\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("original file.txt")).unwrap(),
|
||||
"original\n"
|
||||
);
|
||||
assert!(!editor.dirty(), "{command}");
|
||||
assert!(!editor.should_quit(), "{command}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quit_and_q_refuse_dirty_buffers_then_exit_when_clean() {
|
||||
for command in ["quit", "q"] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor("file.txt", "original\n");
|
||||
insert(&mut editor, "changed ");
|
||||
|
||||
editor.execute(command);
|
||||
|
||||
assert!(!editor.should_quit(), "{command}");
|
||||
assert!(editor.dirty(), "{command}");
|
||||
assert_eq!(editor.context().text, "changed original\n");
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("file.txt")).unwrap(),
|
||||
"original\n"
|
||||
);
|
||||
|
||||
editor.handle(Input::Char('u'));
|
||||
assert!(!editor.dirty(), "{command}");
|
||||
editor.execute(command);
|
||||
assert!(editor.should_quit(), "{command}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_quit_and_q_bang_validate_arguments_before_discarding_changes() {
|
||||
for command in ["force_quit", "q!"] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor("file.txt", "original\n");
|
||||
insert(&mut editor, "changed ");
|
||||
|
||||
editor.execute(&format!("{command} unexpected argument"));
|
||||
|
||||
assert!(!editor.should_quit(), "{command}");
|
||||
assert!(editor.dirty(), "{command}");
|
||||
assert_eq!(editor.context().text, "changed original\n");
|
||||
|
||||
editor.execute(command);
|
||||
assert!(editor.should_quit(), "{command}");
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("file.txt")).unwrap(),
|
||||
"original\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_next_and_bnext_preserve_each_buffers_dirty_text_and_undo() {
|
||||
for command in ["buffer_next", "bnext"] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor("first.txt", "first\n");
|
||||
let first_id = editor.buffer_id();
|
||||
insert(&mut editor, "changed ");
|
||||
let second_path = fixture.0.join("second file.txt");
|
||||
fs::write(&second_path, "second\n").unwrap();
|
||||
editor.execute(&format!("edit {}", second_path.display()));
|
||||
let second_id = editor.buffer_id();
|
||||
assert_ne!(first_id, second_id);
|
||||
insert(&mut editor, "edited ");
|
||||
|
||||
editor.execute(command);
|
||||
|
||||
assert_eq!(editor.buffer_id(), first_id, "{command}");
|
||||
assert_eq!(editor.context().text, "changed first\n");
|
||||
assert!(editor.dirty(), "{command}");
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "first\n");
|
||||
assert!(!editor.dirty(), "{command}");
|
||||
|
||||
editor.execute(command);
|
||||
|
||||
assert_eq!(editor.buffer_id(), second_id, "{command}");
|
||||
assert_eq!(editor.context().text, "edited second\n");
|
||||
assert!(editor.dirty(), "{command}");
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "second\n");
|
||||
assert!(!editor.dirty(), "{command}");
|
||||
assert_eq!(editor.buffer_count(), 2);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("first.txt")).unwrap(),
|
||||
"first\n"
|
||||
);
|
||||
assert_eq!(fs::read_to_string(&second_path).unwrap(), "second\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_canonical_and_alias_collisions_reject_the_entire_feature() {
|
||||
for reserved in ["save", "w", "quit", "q"] {
|
||||
let mut editor = Editor::open(None, vec!["text".into()]).unwrap();
|
||||
let feature = |commands: Vec<String>| Feature {
|
||||
name: "example".into(),
|
||||
required_capabilities: vec!["text".into()],
|
||||
commands,
|
||||
core: false,
|
||||
};
|
||||
|
||||
assert!(
|
||||
editor
|
||||
.register(feature(vec!["example.run".into(), reserved.into()]))
|
||||
.is_err(),
|
||||
"{reserved}"
|
||||
);
|
||||
assert!(editor.execute("example.run").is_none(), "{reserved}");
|
||||
|
||||
editor
|
||||
.register(feature(vec!["example.run".into()]))
|
||||
.unwrap();
|
||||
let invocation = editor.execute("example.run remaining args").unwrap();
|
||||
assert_eq!(invocation.plugin, "example");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user