use kui_core::access::{AccessAction, AccessRequest, Role};
use kui_core::input::{InputEvent, KeyCode, KeyMods, KeyPress, UiEvent};
use kui_core::spec::NodeSpec;
use kui_core::testing::{kinds, press, release};
use kui_core::{Core, Key, Size, Value, Vec2, widgets};
fn node(core: &mut Core, key: Key) -> kui_core::access::AccessNode {
core.access_tree()
.nodes
.iter()
.find(|n| n.key == key)
.cloned()
.expect("the node is in the access tree")
}
fn key(core: &mut Core, code: KeyCode) -> Vec<UiEvent> {
core.press(KeyPress::new(code, KeyMods::default()))
}
fn value(ev: &UiEvent) -> (f64, &str) {
(
ev.payload.get_float("value").unwrap(),
ev.payload.get_str("phase").unwrap(),
)
}
#[test]
fn a_checkbox_reads_its_state_and_presses_through_its_payload() {
let mut core = Core::new();
let build = |core: &mut Core, on: bool, mixed: bool| {
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let m = ui.metrics();
let a = widgets::checkbox(&mut ui, "Mute", on, "mute");
let b = widgets::toggle_with(
&mut ui,
widgets::Toggle::Checkbox,
"all",
"Select all",
widgets::toggle_spec(&m).mixed(mixed).on_click("all"),
None,
);
ui.finish();
(a, b)
};
let keys = build(&mut core, false, true);
let mute = node(&mut core, keys.0);
assert_eq!(mute.role, Role::Checkbox);
assert_eq!(mute.name.as_deref(), Some("Mute"));
assert_eq!((mute.checked, mute.mixed), (Some(false), false));
let all = node(&mut core, keys.1);
assert!(all.mixed, "the select-all box reads as mixed");
key(&mut core, KeyCode::Tab);
assert_eq!(core.focus(), Some(keys.0));
let evs = key(&mut core, KeyCode::Space);
assert_eq!(evs.len(), 1);
assert_eq!(evs[0].payload.as_str(), Some("mute"));
build(&mut core, true, false);
assert_eq!(node(&mut core, keys.0).checked, Some(true));
}
#[test]
fn a_radio_group_s_arrows_press_the_radio_they_land_on() {
let mut core = Core::new();
let build = |core: &mut Core, current: usize| {
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let k = widgets::radio_group(
&mut ui,
"Theme",
&["Light", "Dark", "System"],
Some(current),
|i| Value::Int(i as i64),
);
ui.finish();
k
};
let group = build(&mut core, 0);
assert_eq!(node(&mut core, group).role, Role::RadioGroup);
key(&mut core, KeyCode::Tab);
let evs = key(&mut core, KeyCode::Down);
assert_eq!(evs.len(), 1, "moving the choice pressed the next radio");
assert_eq!(evs[0].payload.as_int(), Some(1));
build(&mut core, 1);
let checked: Vec<Option<bool>> = core
.access_tree()
.nodes
.iter()
.filter(|n| n.role == Role::Radio)
.map(|n| n.checked)
.collect();
assert_eq!(checked, [Some(false), Some(true), Some(false)]);
}
#[test]
fn a_switch_is_a_switch() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let k = widgets::switch(&mut ui, "Wi-Fi", true, "wifi");
ui.finish();
let n = node(&mut core, k);
assert_eq!((n.role, n.checked), (Role::Switch, Some(true)));
}
fn slider(core: &mut Core, now: f32, disabled: bool) -> Key {
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let m = ui.metrics();
let spec = widgets::slider_spec(&m)
.width(kui_core::Sizing::Fixed(216.0))
.value_now(now)
.value_min(0.0)
.value_max(100.0)
.value_step(10.0)
.on_change("vol")
.disabled(disabled);
let k = widgets::slider_with(&mut ui, "Volume", spec, None);
ui.finish();
k
}
#[test]
fn a_press_proposes_the_value_under_it_and_a_drag_each_new_step() {
let mut core = Core::new();
let k = slider(&mut core, 0.0, false);
let n = node(&mut core, k);
assert_eq!(n.role, Role::Slider);
assert_eq!(n.name.as_deref(), Some("Volume"));
assert_eq!(n.step, Some(10.0));
let evs = press(&mut core, Vec2::new(70.0, 8.0));
assert_eq!(kinds(&evs).last(), Some(&"change"));
let change = evs
.iter()
.find(|e| kinds(std::slice::from_ref(e)) == ["change"])
.unwrap();
assert_eq!(value(change), (30.0, "move"));
assert_eq!(change.key, k);
assert_eq!(change.payload.get_str("tag"), Some("vol"));
let evs = core.handle_input(InputEvent::CursorMoved(Vec2::new(72.0, 8.0)));
assert!(kinds(&evs).iter().all(|k| *k != "change"));
let evs = core.handle_input(InputEvent::CursorMoved(Vec2::new(150.0, 40.0)));
let moved: Vec<(f64, &str)> = evs
.iter()
.filter(|e| e.kind() == Some("change"))
.map(value)
.collect();
assert_eq!(moved, [(70.0, "move")]);
core.handle_input(InputEvent::CursorMoved(Vec2::new(900.0, 8.0)));
let evs = release(&mut core);
let ended: Vec<(f64, &str)> = evs
.iter()
.filter(|e| e.kind() == Some("change"))
.map(value)
.collect();
assert_eq!(ended, [(100.0, "end")]);
assert!(
evs.iter().all(|e| e.payload.as_str() != Some("vol")),
"a slide is not a click"
);
}
#[test]
fn the_keys_move_it_by_its_step_and_to_its_ends() {
let mut core = Core::new();
let k = slider(&mut core, 40.0, false);
key(&mut core, KeyCode::Tab);
assert_eq!(core.focus(), Some(k));
let step = |core: &mut Core, code| {
let evs = key(core, code);
evs.iter()
.filter(|e| e.kind() == Some("change"))
.map(|e| value(e).0)
.collect::<Vec<_>>()
};
assert_eq!(step(&mut core, KeyCode::Right), [50.0]);
assert_eq!(step(&mut core, KeyCode::Down), [30.0]);
assert_eq!(step(&mut core, KeyCode::PageUp), [100.0]);
assert_eq!(step(&mut core, KeyCode::Home), [0.0]);
assert_eq!(step(&mut core, KeyCode::End), [100.0]);
slider(&mut core, 100.0, false);
assert_eq!(step(&mut core, KeyCode::Up), [] as [f64; 0]);
slider(&mut core, 40.0, false);
let evs = core.handle_input(InputEvent::Access(AccessRequest::new(
k,
AccessAction::Increment,
)));
assert_eq!(value(&evs[0]), (50.0, "end"));
}
#[test]
fn a_disabled_slider_proposes_nothing() {
let mut core = Core::new();
let k = slider(&mut core, 40.0, true);
let evs = press(&mut core, Vec2::new(70.0, 8.0));
release(&mut core);
assert!(kinds(&evs).iter().all(|k| *k != "change"));
let evs = core.handle_input(InputEvent::Access(AccessRequest::new(
k,
AccessAction::Increment,
)));
assert!(evs.is_empty());
}
#[test]
fn a_slider_without_on_change_still_nudges() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let k = ui.leaf_keyed(
"vol",
NodeSpec::row()
.width(kui_core::Sizing::Fixed(100.0))
.height(kui_core::Sizing::Fixed(10.0))
.role(Role::Slider)
.label("Volume")
.value_now(3.0)
.on_drag("vol"),
);
ui.finish();
key(&mut core, KeyCode::Tab);
assert_eq!(core.focus(), Some(k));
assert_eq!(kinds(&key(&mut core, KeyCode::Right)), ["access"]);
assert!(key(&mut core, KeyCode::End).is_empty());
let evs = core.handle_input(InputEvent::Access(
AccessRequest::new(k, AccessAction::SetValue).with_value("7.5"),
));
assert_eq!(kinds(&evs), ["access"]);
assert_eq!(evs[0].payload.get_str("action"), Some("setValue"));
assert_eq!(evs[0].payload.get_float("value"), Some(7.5));
}
#[test]
fn a_reader_s_set_value_is_proposed_snapped_and_clamped() {
let mut core = Core::new();
let k = slider(&mut core, 40.0, false);
assert!(node(&mut core, k).supports(AccessAction::SetValue));
let set = |core: &mut Core, v: &str| {
let evs = core.handle_input(InputEvent::Access(
AccessRequest::new(k, AccessAction::SetValue).with_value(v),
));
evs.iter()
.map(value)
.map(|(v, p)| (v, p.to_string()))
.collect::<Vec<_>>()
};
assert_eq!(set(&mut core, "72"), [(70.0, "end".to_string())]);
assert_eq!(set(&mut core, "250"), [(100.0, "end".to_string())]);
assert_eq!(set(&mut core, "41"), [], "where it is: nothing");
assert_eq!(set(&mut core, "25 minutes"), [], "not a number: nothing");
let k = slider(&mut core, 40.0, true);
assert!(!node(&mut core, k).supports(AccessAction::SetValue));
assert_eq!(set(&mut core, "72"), []);
}
#[test]
fn a_fractional_step_proposes_the_decimal() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(400.0, 200.0), 1.0);
let k = widgets::slider(&mut ui, "Gain", 0.2, 0.0, 1.0, 0.1, "gain");
ui.finish();
key(&mut core, KeyCode::Tab);
assert_eq!(core.focus(), Some(k));
let evs = key(&mut core, KeyCode::Right);
assert_eq!(value(&evs[0]).0, 0.3);
}