use taino_edit_core::{
base_keymap, EditorState, KeyPress, Keymap, Node, NodeSpec, Schema, SchemaBuilder, Selection,
Transaction,
};
fn schema() -> Schema {
SchemaBuilder::new()
.node(
"doc",
NodeSpec {
content: Some("block+".into()),
..Default::default()
},
)
.node(
"paragraph",
NodeSpec {
content: Some("inline*".into()),
group: Some("block".into()),
..Default::default()
},
)
.node(
"text",
NodeSpec {
group: Some("inline".into()),
..Default::default()
},
)
.top_node("doc")
.build()
.unwrap()
}
fn doc(s: &Schema, blocks: Vec<&str>) -> Node {
let ps: Vec<Node> = blocks
.iter()
.map(|t| {
s.node(
"paragraph",
Default::default(),
vec![s.text(t, vec![]).unwrap()],
vec![],
)
.unwrap()
})
.collect();
s.node("doc", Default::default(), ps, vec![]).unwrap()
}
fn at(st: EditorState, pos: usize) -> EditorState {
let mut t = st.tr();
t.set_selection(Selection::caret(pos));
st.apply(t)
}
fn press(km: &Keymap, st: &EditorState, k: KeyPress) -> Option<EditorState> {
let mut out = None;
{
let mut d = |tx: Transaction| out = Some(st.apply(tx));
km.handle(st, &k, Some(&mut d));
}
out
}
#[test]
fn mod_is_ctrl_off_mac_and_cmd_on_mac() {
let s = schema();
let st = EditorState::new(doc(&s, vec!["Hello"]), s.clone());
let pc = base_keymap(false);
assert!(pc.handle(&st, &KeyPress::key("a").ctrl(), None));
assert!(!pc.handle(&st, &KeyPress::key("a").meta(), None));
let mac = base_keymap(true);
assert!(mac.handle(&st, &KeyPress::key("a").meta(), None));
assert!(!mac.handle(&st, &KeyPress::key("a").ctrl(), None));
let next = press(&pc, &st, KeyPress::key("a").ctrl()).unwrap();
assert_eq!(next.selection(), Selection::All);
}
#[test]
fn enter_splits_the_block() {
let s = schema();
let st = at(EditorState::new(doc(&s, vec!["abcd"]), s.clone()), 3);
let km = base_keymap(false);
let out = press(&km, &st, KeyPress::key("Enter")).unwrap();
assert_eq!(out.doc().child_count(), 2);
assert_eq!(out.doc().child(1).text_content(), "cd");
}
#[test]
fn backspace_chain_covers_char_and_block_join() {
let s = schema();
let km = base_keymap(false);
let st = at(EditorState::new(doc(&s, vec!["abc"]), s.clone()), 4);
let out = press(&km, &st, KeyPress::key("Backspace")).unwrap();
assert_eq!(out.doc().text_content(), "ab");
let two = at(EditorState::new(doc(&s, vec!["ab", "cd"]), s.clone()), 5);
let joined = press(&km, &two, KeyPress::key("Backspace")).unwrap();
assert_eq!(joined.doc().child_count(), 1);
assert_eq!(joined.doc().text_content(), "abcd");
}
#[test]
fn delete_chain_pulls_next_block() {
let s = schema();
let km = base_keymap(false);
let st = at(EditorState::new(doc(&s, vec!["ab", "cd"]), s.clone()), 3);
let out = press(&km, &st, KeyPress::key("Delete")).unwrap();
assert_eq!(out.doc().child_count(), 1);
assert_eq!(out.doc().text_content(), "abcd");
}
#[test]
fn caret_motion_keys() {
let s = schema();
let km = base_keymap(false);
let st = at(EditorState::new(doc(&s, vec!["abcd"]), s.clone()), 3);
assert_eq!(
press(&km, &st, KeyPress::key("ArrowLeft"))
.unwrap()
.selection(),
Selection::caret(2)
);
assert_eq!(
press(&km, &st, KeyPress::key("ArrowRight"))
.unwrap()
.selection(),
Selection::caret(4)
);
assert_eq!(
press(&km, &st, KeyPress::key("Home")).unwrap().selection(),
Selection::caret(1)
);
assert_eq!(
press(&km, &st, KeyPress::key("End")).unwrap().selection(),
Selection::caret(5)
);
}
#[test]
fn shift_is_implicit_for_symbol_keys() {
use taino_edit_core::{select_all, Command};
let mut km = base_keymap(false);
let hit = std::rc::Rc::new(std::cell::Cell::new(false));
let h = hit.clone();
let cmd: Command = Box::new(move |_, _| {
h.set(true);
true
});
km.add("Mod->", cmd);
let s = schema();
let st = EditorState::new(doc(&s, vec!["x"]), s.clone());
assert!(
km.handle(&st, &KeyPress::key(">").ctrl().shift(), None),
"Mod-> must match a Ctrl+Shift+> press"
);
assert!(hit.get(), "the bound command must have run");
let mut km = base_keymap(false);
km.add("Mod-z", Box::new(select_all));
assert!(km.handle(&st, &KeyPress::key("z").ctrl(), None));
assert!(!km.handle(&st, &KeyPress::key("Z").ctrl().shift(), None));
}
#[test]
fn unknown_key_is_unhandled() {
let s = schema();
let km = base_keymap(false);
let st = EditorState::new(doc(&s, vec!["x"]), s.clone());
assert!(!km.handle(&st, &KeyPress::key("F5"), None));
assert!(km.len() >= 8);
}
#[test]
fn add_chained_tries_new_command_first_then_falls_back() {
use std::cell::Cell;
use std::rc::Rc;
use taino_edit_core::{Command, Keymap};
let order = Rc::new(Cell::new(""));
let o1 = order.clone();
let first: Command = Box::new(move |state, _| {
if state.doc().text_content() == "a" {
o1.set("first");
true
} else {
false
}
});
let o2 = order.clone();
let second: Command = Box::new(move |_state, _| {
o2.set("second");
true
});
let mut km = Keymap::new(false, vec![]);
km.add("Tab", second);
km.add_chained("Tab", first);
let s = schema();
let st_a = EditorState::new(doc(&s, vec!["a"]), s.clone());
assert!(km.handle(&st_a, &KeyPress::key("Tab"), None));
assert_eq!(order.get(), "first");
let st_b = EditorState::new(doc(&s, vec!["b"]), s.clone());
assert!(km.handle(&st_b, &KeyPress::key("Tab"), None));
assert_eq!(order.get(), "second");
}