use kui_core::{
Core, EditKey, EditOptions, InputEvent, Key, KeyCode, KeyMods, KeyPress, Mods, NodeSpec, Size,
UiEvent, Value, Vec2,
};
fn frame(core: &mut Core, opts: &EditOptions, shell: bool) -> (Key, Key) {
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
ui.configure_root(NodeSpec::column().fill());
let mut field = Key::ROOT;
let body = |ui: &mut kui_core::Ui<'_>, field: &mut Key| {
*field = ui.text_edit(
"field",
"ab",
&EditOptions {
autofocus: true,
..opts.clone()
},
NodeSpec::column().size(200.0, 40.0),
);
ui.leaf_keyed(
"after",
NodeSpec::row()
.size(100.0, 20.0)
.on_click("after")
.label("After"),
);
};
let shell_key = if shell {
ui.with_keyed(
"shell",
NodeSpec::column().fill().on_key("shell").key_up(),
|ui| body(ui, &mut field),
)
} else {
body(&mut ui, &mut field);
Key::ROOT
};
ui.finish();
(shell_key, field)
}
fn rig(opts: EditOptions, shell: bool) -> (Core, Key, Key) {
let mut core = Core::new();
let (s, f) = frame(&mut core, &opts, shell);
frame(&mut core, &opts, shell);
assert_eq!(core.focus(), Some(f), "the field took focus");
(core, s, f)
}
fn primary() -> KeyMods {
if cfg!(target_os = "macos") {
KeyMods {
super_key: true,
..KeyMods::default()
}
} else {
KeyMods {
ctrl: true,
..KeyMods::default()
}
}
}
fn press(core: &mut Core, code: KeyCode, mods: KeyMods, text: Option<&str>) -> Vec<UiEvent> {
let kp = KeyPress {
text: text.map(str::to_string),
..KeyPress::new(code, mods)
};
let mut out = core.handle_input(InputEvent::KeyDown(kp.clone()));
if let Some(ev) = kp.edit_event() {
out.extend(core.handle_input(ev));
}
out.extend(core.handle_input(InputEvent::KeyUp(kp.released())));
out
}
fn heard(evs: &[UiEvent], sink: Key) -> Vec<(String, String)> {
evs.iter()
.filter(|e| e.key == sink && e.kind() == Some("key"))
.map(|e| {
let at = |k: &str| e.payload.get_str(k).unwrap_or("").to_string();
(at("phase"), at("code"))
})
.collect()
}
fn boundary(evs: &[UiEvent], field: Key) -> Option<(String, String)> {
let e = evs
.iter()
.find(|e| e.key == field && e.kind() == Some("boundary"))?;
let at = |k: &str| e.payload.get_str(k).unwrap_or("").to_string();
Some((at("key"), at("edge")))
}
fn edit(core: &mut Core, ek: EditKey, mods: Mods) -> Vec<UiEvent> {
core.handle_input(InputEvent::Key(ek, mods))
}
const NONE: Mods = Mods {
shift: false,
word: false,
doc: false,
};
#[test]
fn a_chord_the_editor_does_not_act_on_reaches_the_sink_above_it() {
let (mut core, shell, field) = rig(EditOptions::default(), true);
let evs = press(&mut core, KeyCode::Char('n'), primary(), None);
assert_eq!(
heard(&evs, shell),
[("down".into(), "n".into()), ("up".into(), "n".into())],
"the press and its release, on the shell"
);
assert_eq!(core.edit_text(field).as_deref(), Some("ab"));
assert_eq!(core.focus(), Some(field), "focus stays in the field");
let down = evs.iter().find(|e| e.key == shell).unwrap();
assert_eq!(down.payload.get("tag"), Some(&Value::str("shell")));
let ctrl = KeyMods {
ctrl: true,
..KeyMods::default()
};
let evs = press(&mut core, KeyCode::Char('k'), ctrl, None);
assert_eq!(heard(&evs, shell)[0], ("down".into(), "k".into()));
}
#[test]
fn the_editor_keeps_what_it_acts_on() {
let (mut core, shell, field) = rig(EditOptions::default(), true);
for c in ['c', 'x', 'v', 'a', 'z', 'y'] {
assert!(heard(&press(&mut core, KeyCode::Char(c), primary(), None), shell).is_empty());
}
let shifted = KeyMods {
shift: true,
..primary()
};
assert!(heard(&press(&mut core, KeyCode::Char('Z'), shifted, None), shell).is_empty());
let alt = KeyMods {
alt: true,
..KeyMods::default()
};
let evs = [
press(&mut core, KeyCode::Char('q'), KeyMods::default(), Some("q")),
press(&mut core, KeyCode::Char('o'), alt, Some("ø")),
press(&mut core, KeyCode::Left, primary(), None),
press(&mut core, KeyCode::Backspace, alt, None),
press(&mut core, KeyCode::Enter, primary(), None),
];
for e in &evs {
assert!(heard(e, shell).is_empty(), "{e:?}");
}
assert_eq!(core.focus(), Some(field));
}
#[test]
fn with_no_sink_above_a_chord_goes_nowhere() {
let (mut core, _, field) = rig(EditOptions::default(), false);
let evs = press(&mut core, KeyCode::Char('n'), primary(), None);
assert!(evs.iter().all(|e| e.kind() != Some("key")));
assert_eq!(core.edit_text(field).as_deref(), Some("ab"));
}
#[test]
fn a_key_at_the_edge_of_the_text_says_which_edge() {
let (mut core, _, field) = rig(EditOptions::default(), true);
let at = |evs: &[UiEvent]| boundary(evs, field);
assert_eq!(
at(&edit(&mut core, EditKey::Right, NONE)),
Some(("right".into(), "end".into()))
);
assert_eq!(
at(&edit(&mut core, EditKey::Down, NONE)),
Some(("down".into(), "end".into())),
"a field has no line below"
);
assert_eq!(
at(&edit(&mut core, EditKey::Delete, NONE)),
Some(("delete".into(), "end".into()))
);
assert_eq!(at(&edit(&mut core, EditKey::Left, NONE)), None);
assert_eq!(at(&edit(&mut core, EditKey::Left, NONE)), None);
assert_eq!(
at(&edit(&mut core, EditKey::Left, NONE)),
Some(("left".into(), "start".into()))
);
assert_eq!(
at(&edit(&mut core, EditKey::Up, NONE)),
Some(("up".into(), "start".into()))
);
assert_eq!(
at(&edit(&mut core, EditKey::Backspace, NONE)),
Some(("backspace".into(), "start".into()))
);
assert_eq!(core.edit_text(field).as_deref(), Some("ab"));
let shift = Mods {
shift: true,
..NONE
};
assert_eq!(at(&edit(&mut core, EditKey::Left, shift)), None);
}
#[test]
fn a_selection_is_not_an_edge() {
let (mut core, _, field) = rig(EditOptions::default(), true);
edit(&mut core, EditKey::SelectAll, NONE);
assert_eq!(
boundary(&edit(&mut core, EditKey::Right, NONE), field),
None
);
assert_eq!(
boundary(&edit(&mut core, EditKey::Right, NONE), field),
Some(("right".into(), "end".into()))
);
}
#[test]
fn a_document_reports_its_first_and_last_lines() {
let opts = EditOptions {
multiline: true,
..EditOptions::default()
};
let mut core = Core::new();
let f = |core: &mut Core| {
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
ui.configure_root(NodeSpec::column().fill());
let k = ui.text_edit(
"doc",
"one\ntwo",
&EditOptions {
autofocus: true,
..opts.clone()
},
NodeSpec::column().size(300.0, 100.0),
);
ui.finish();
k
};
let doc = f(&mut core);
f(&mut core);
assert_eq!(
boundary(&edit(&mut core, EditKey::Up, NONE), doc),
Some(("up".into(), "start".into()))
);
assert_eq!(boundary(&edit(&mut core, EditKey::Down, NONE), doc), None);
assert_eq!(
boundary(&edit(&mut core, EditKey::Down, NONE), doc),
Some(("down".into(), "end".into()))
);
}
#[test]
fn tab_walks_the_ring_from_a_field_unless_it_keeps_tab() {
let (mut core, shell, _) = rig(EditOptions::default(), true);
let evs = press(&mut core, KeyCode::Tab, KeyMods::default(), None);
assert!(heard(&evs, shell).is_empty());
assert_ne!(core.focus().map(|k| k.0), None);
let after = core.focus();
let keep = EditOptions {
keep_tab: true,
..EditOptions::default()
};
let (mut core, shell, field) = rig(keep, true);
let evs = press(&mut core, KeyCode::Tab, KeyMods::default(), None);
assert_eq!(
heard(&evs, shell),
[("down".into(), "tab".into()), ("up".into(), "tab".into())]
);
assert_eq!(core.focus(), Some(field), "focus stayed in the field");
assert_ne!(after, Some(field));
let shift = KeyMods {
shift: true,
..KeyMods::default()
};
let evs = press(&mut core, KeyCode::Tab, shift, None);
assert_eq!(heard(&evs, shell)[0], ("down".into(), "tab".into()));
assert_eq!(core.focus(), Some(field));
assert_eq!(core.edit_text(field).as_deref(), Some("ab"), "no tab typed");
}
#[test]
fn up_and_down_on_the_edge_line_report_wherever_the_caret_is() {
let (mut core, _, field) = rig(EditOptions::default(), true);
edit(&mut core, EditKey::Left, NONE);
assert_eq!(
boundary(&edit(&mut core, EditKey::Down, NONE), field),
Some(("down".into(), "end".into())),
"the first ↓ from the middle"
);
edit(&mut core, EditKey::Home, NONE);
edit(&mut core, EditKey::Right, NONE);
assert_eq!(
boundary(&edit(&mut core, EditKey::Up, NONE), field),
Some(("up".into(), "start".into()))
);
}
#[test]
fn an_editors_caret_is_readable_and_placeable_by_point() {
let (mut core, _, field) = rig(EditOptions::default(), true);
let end = core.edit_caret_rect(field).expect("a caret");
edit(&mut core, EditKey::Home, NONE);
let start = core.edit_caret_rect(field).expect("a caret");
assert!(end.x > start.x && (end.y - start.y).abs() < 0.5);
assert!(end.h > 0.0);
assert!(core.edit_caret_to(field, Vec2::new(end.x + 50.0, end.y + 2.0)));
assert_eq!(core.edit.caret_and_selection(field), Some((2, None)));
assert!(core.edit_caret_to(field, Vec2::new(start.x - 30.0, start.y + 2.0)));
assert_eq!(core.edit.caret_and_selection(field), Some((0, None)));
assert_eq!(core.focus(), Some(field));
assert!(!core.edit_caret_to(Key::ROOT, Vec2::new(0.0, 0.0)));
}