#![cfg(feature = "mock")]
use std::time::{Duration, Instant};
use crate::{Button, Device, Error, FrameType, Health, LogLevel, MockBox, RebootTarget, Version};
#[test]
fn query_returns_configured_values_and_records_commands() {
let mock = MockBox::new()
.with_version(Version {
proto_ver: 2,
fw_major: 5,
fw_minor: 6,
fw_patch: 7,
})
.with_health(Health::from_flags(0x0F));
let device = Device::with_mock(mock.clone());
let v = device.query_version().unwrap();
assert_eq!((v.fw_major, v.fw_minor, v.fw_patch), (5, 6, 7));
let h = device.query_health().unwrap();
assert!(h.link_up && h.mouse_attached && h.clone_configured && h.injection_active);
device.press(Button::Left).unwrap();
let frames = mock.recorded_frames();
let button = frames
.iter()
.find(|f| f.ty == FrameType::Inject)
.expect("press recorded");
assert_eq!(button.payload, vec![0, 0, 0, 1]); assert!(mock.saw(FrameType::Inject));
}
#[test]
fn pushed_logs_reach_the_logs_channel_in_order() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let rx = device.logs();
mock.push_log(LogLevel::Warn, "overheating");
mock.push_log(LogLevel::Info, "recovered");
let a = rx.recv_timeout(Duration::from_secs(1)).unwrap();
let b = rx.recv_timeout(Duration::from_secs(1)).unwrap();
assert_eq!((a.level, a.text.as_str()), (LogLevel::Warn, "overheating"));
assert_eq!((b.level, b.text.as_str()), (LogLevel::Info, "recovered"));
}
#[test]
fn set_health_updates_subsequent_queries() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
assert!(!device.query_health().unwrap().mouse_attached);
mock.set_health(Health::from_flags(0x02));
assert!(device.query_health().unwrap().mouse_attached);
}
#[test]
fn handshake_accepts_matching_proto_ver() {
let device = Device::open_mock(MockBox::new()).expect("default proto_ver matches");
assert_eq!(device.query_version().unwrap().proto_ver, 2);
}
#[test]
fn handshake_rejects_wrong_proto_ver() {
let mock = MockBox::new().with_version(Version {
proto_ver: 9,
fw_major: 0,
fw_minor: 0,
fw_patch: 0,
});
let err = Device::open_mock(mock).unwrap_err();
assert!(matches!(err, Error::BadProtoVer { got: 9 }), "got {err:?}");
}
#[test]
fn handshake_on_silent_box_is_no_reply() {
let err = Device::open_mock(MockBox::new().silent()).unwrap_err();
assert!(matches!(err, Error::NoReply), "got {err:?}");
}
#[test]
fn dropping_the_last_clone_joins_threads_without_hanging() {
let device = Device::with_mock(MockBox::new());
device.move_rel(1, 1).unwrap();
let start = Instant::now();
drop(device);
assert!(
start.elapsed() < Duration::from_millis(500),
"drop took too long: {:?}",
start.elapsed()
);
}
#[test]
fn a_clone_keeps_the_reader_alive_until_the_last_drop() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let clone = device.clone();
drop(device);
let rx = clone.logs();
mock.push_log(LogLevel::Info, "still here");
assert_eq!(
rx.recv_timeout(Duration::from_secs(1)).unwrap().text,
"still here"
);
}
#[test]
fn device_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Device>();
}
#[test]
fn inject_and_move_verbs_dispatch_to_the_right_class() {
use crate::{Input, Key, Motion};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.inject(Key::A, crate::Action::Press).unwrap(); device
.inject(Input::Button(Button::Left), crate::Action::Press)
.unwrap();
device.move_axis(Motion::Wheel(-2)).unwrap();
let payloads: Vec<(FrameType, Vec<u8>)> = mock
.recorded_frames()
.into_iter()
.filter(|f| f.ty == FrameType::Inject || f.ty == FrameType::Move)
.map(|f| (f.ty, f.payload))
.collect();
assert_eq!(payloads[0], (FrameType::Inject, vec![1, 0x04, 0x00, 1])); assert_eq!(payloads[1], (FrameType::Inject, vec![0, 0, 0, 1])); assert_eq!(payloads[2], (FrameType::Move, vec![1, 0xFE, 0xFF])); drop(device);
}
#[test]
fn reapply_re_emits_only_held_overrides() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.press(Button::Left).unwrap();
device.force_release(Button::Side1).unwrap();
device.press(Button::Middle).unwrap();
device.soft_release(Button::Middle).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let buttons: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Inject)
.map(|f| f.payload.clone())
.collect();
assert_eq!(buttons, vec![vec![0, 0, 0, 1], vec![0, 3, 0, 2]]);
drop(device);
}
#[test]
fn reapply_re_emits_held_locks_but_not_released_ones() {
use crate::{Blanket, Key, LockDirection, LockTarget};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.lock(LockTarget::X, LockDirection::Positive).unwrap();
device.lock_key(Key::A, LockDirection::Both).unwrap();
device.lock_all(Blanket::Keys).unwrap();
device.unlock_key(Key::A, LockDirection::Both).unwrap(); mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert_eq!(locks, vec![vec![0, 0, 0, 1, 1], vec![3, 0, 0, 0, 1]]);
drop(device);
}
#[test]
fn reboot_emits_the_target_byte() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
for target in [
RebootTarget::DeviceRun,
RebootTarget::HostRun,
RebootTarget::DeviceDownload,
RebootTarget::HostDownload,
] {
device.reboot(target).unwrap();
}
let reboots: Vec<u8> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::RebootDl)
.map(|f| f.payload[0])
.collect();
assert_eq!(reboots, vec![2, 3, 0, 1]);
drop(device);
}