lesavka/testing/tests/client_keyboard_activation_contract.rs

113 lines
3.6 KiB
Rust
Raw Normal View History

//! Focused coverage for keyboard quick-toggle activation edges.
//!
//! Scope: exercise the keyboard aggregator's recent-press tracking directly.
//! Targets: `client/src/input/keyboard.rs`.
//! Why: the swap key needs to stay reliable even when a tap begins and ends
//! before the next launcher/input poll cycle observes the key state.
mod keymap {
pub use lesavka_client::input::keymap::*;
}
#[allow(warnings)]
mod keyboard_activation_contract {
include!(env!("LESAVKA_CLIENT_KEYBOARD_SRC"));
use evdev::AttributeSet;
use evdev::uinput::VirtualDevice;
use serial_test::serial;
use std::thread;
fn open_virtual_device(vdev: &mut VirtualDevice) -> Option<evdev::Device> {
for _ in 0..40 {
if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking() {
if let Some(Ok(path)) = nodes.next() {
if let Ok(dev) = evdev::Device::open(path) {
let _ = dev.set_nonblocking(true);
return Some(dev);
}
}
}
thread::sleep(std::time::Duration::from_millis(10));
}
None
}
fn build_keyboard(name: &str) -> Option<(VirtualDevice, evdev::Device)> {
let mut keys = AttributeSet::<evdev::KeyCode>::new();
keys.insert(evdev::KeyCode::KEY_A);
keys.insert(evdev::KeyCode::KEY_ENTER);
let mut vdev = VirtualDevice::builder()
.ok()?
.name(name)
.with_keys(&keys)
.ok()?
.build()
.ok()?;
let dev = open_virtual_device(&mut vdev)?;
Some((vdev, dev))
}
fn new_aggregator(
dev: evdev::Device,
) -> (
KeyboardAggregator,
tokio::sync::broadcast::Receiver<KeyboardReport>,
) {
let (tx, rx) = tokio::sync::broadcast::channel(16);
(KeyboardAggregator::new(dev, false, tx, None), rx)
}
#[test]
#[serial]
fn take_key_activation_consumes_a_fast_tap_once() {
let Some((mut vdev, dev)) = build_keyboard("lesavka-kbd-activation-tap") else {
return;
};
let (mut agg, _rx) = new_aggregator(dev);
vdev.emit(&[
evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 1),
evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 0),
])
.expect("emit key tap");
thread::sleep(std::time::Duration::from_millis(20));
agg.process_events();
assert!(agg.take_key_activation(evdev::KeyCode::KEY_A));
assert!(!agg.take_key_activation(evdev::KeyCode::KEY_A));
}
#[test]
#[serial]
fn process_events_clears_stale_recent_key_presses_before_polling() {
let Some((_vdev, dev)) = build_keyboard("lesavka-kbd-activation-clear") else {
return;
};
let (mut agg, _rx) = new_aggregator(dev);
agg.recent_key_presses.insert(evdev::KeyCode::KEY_A);
agg.process_events();
assert!(!agg.take_key_activation(evdev::KeyCode::KEY_A));
}
#[test]
#[serial]
fn reset_state_clears_recent_key_presses_even_when_idle() {
let Some((_vdev, dev)) = build_keyboard("lesavka-kbd-activation-reset") else {
return;
};
let (mut agg, mut rx) = new_aggregator(dev);
agg.recent_key_presses.insert(evdev::KeyCode::KEY_A);
agg.reset_state();
assert!(agg.recent_key_presses.is_empty());
let pkt = rx.try_recv().expect("empty report after idle reset");
assert_eq!(pkt.data, vec![0; 8]);
}
}