#[cfg(feature = "mock")]
use crate::protocol::FrameType;
use crate::protocol::command::catch_payload;
use crate::protocol::opcode::{CATCH_BUTTONS, CATCH_KEYS, CATCH_MOTION, CATCH_WHEEL};
use crate::protocol::{Resp, parse_resp};
use crate::types::{Button, CatchMask, CatchState, Health, MouseEvent};
#[test]
fn catch_payload_bytes() {
assert_eq!(catch_payload(CatchMask::all().bits()), [0x0F]);
assert_eq!(catch_payload(0), [0x00]);
}
#[test]
fn catch_mask_class_bits_and_ops() {
assert_eq!(CatchMask::MOTION.bits(), CATCH_MOTION);
assert_eq!(CatchMask::WHEEL.bits(), CATCH_WHEEL);
assert_eq!(CatchMask::BUTTONS.bits(), CATCH_BUTTONS);
assert_eq!(CatchMask::KEYS.bits(), CATCH_KEYS);
assert_eq!(CatchMask::all().bits(), 0x0F);
assert!(CatchMask::empty().is_empty());
let m = CatchMask::MOTION | CatchMask::BUTTONS;
assert_eq!(m.bits(), 0x05);
assert!(m.contains(CatchMask::MOTION));
assert!(m.contains(CatchMask::BUTTONS));
assert!(!m.contains(CatchMask::WHEEL));
assert_eq!(
CatchMask::MOTION | CatchMask::WHEEL | CatchMask::BUTTONS | CatchMask::KEYS,
CatchMask::all()
);
assert_eq!(CatchMask::from_bits_truncate(0xFF), CatchMask::all());
assert_eq!(CatchMask::from_bits_truncate(0xF0), CatchMask::empty());
}
#[test]
fn input_report_decodes_snapshot() {
let r = MouseEvent::from_payload(&[0x09, 0x2C, 0x01, 0xCE, 0xFF, 0xFF, 0xFF]).unwrap();
assert_eq!(r.buttons, 0x09);
assert_eq!(r.dx, 300);
assert_eq!(r.dy, -50);
assert_eq!(r.wheel, -1);
assert!(r.is_pressed(Button::Left));
assert!(r.is_pressed(Button::Side1));
assert!(!r.is_pressed(Button::Right));
assert!(!r.is_pressed(Button::Side2));
}
#[test]
fn input_report_truncated_is_none() {
assert!(MouseEvent::from_payload(&[0, 0, 0, 0, 0, 0]).is_none()); }
#[test]
fn catch_state_decodes_mask_and_drops() {
let c = CatchState::from_payload(&[7, 0x04, 0x04, 0x03, 0x02, 0x01]).unwrap();
assert_eq!(c.mask, CatchMask::BUTTONS);
assert_eq!(c.dropped, 0x01020304);
assert!(CatchState::from_payload(&[7, 0, 0, 0, 0]).is_none()); }
#[test]
fn decode_catch_through_parse_resp() {
let Some(Resp::Catch(c)) = parse_resp(&[7, 0x0F, 0, 0, 0, 0]) else {
panic!("expected Catch");
};
assert_eq!(c.mask, CatchMask::all());
assert_eq!(c.dropped, 0);
}
#[test]
fn health_catch_on_bit_roundtrips() {
let h = Health::from_flags(0x40);
assert!(h.catch_on);
assert!(!h.lock_on && !h.link_up);
assert_eq!(h.to_flags(), 0x40);
assert_eq!(Health::from_flags(0x7F).to_flags(), 0x7F);
}
#[cfg(feature = "mock")]
#[test]
fn dropping_the_stream_unsubscribes() {
use crate::{CatchMask, Device, MockBox};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
{
let _stream = device.catch_events(CatchMask::all()).unwrap();
} let catch_frames: Vec<_> = mock
.recorded_frames()
.into_iter()
.filter(|f| f.ty == FrameType::Catch)
.collect();
assert_eq!(catch_frames.first().unwrap().payload, vec![0x0F]); assert_eq!(catch_frames.last().unwrap().payload, vec![0x00]); }
#[cfg(feature = "mock")]
#[test]
fn pushed_event_arrives_on_the_stream() {
use crate::{Button, CatchEvent, CatchMask, Device, MockBox, MouseEvent};
use std::time::Duration;
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let stream = device.catch_events(CatchMask::all()).unwrap();
mock.push_event(
0,
MouseEvent {
buttons: 0x08,
dx: 5,
dy: -7,
wheel: 1,
},
);
let event = stream
.recv_timeout(Duration::from_secs(1))
.expect("event delivered");
let CatchEvent::Mouse(r) = event else {
panic!("expected a mouse event, got {event:?}");
};
assert_eq!((r.dx, r.dy, r.wheel), (5, -7, 1));
assert!(r.is_pressed(Button::Side1));
}
#[cfg(feature = "mock")]
#[test]
fn catch_buffer_drops_oldest_on_overflow() {
use crate::{Button, CatchEvent, CatchMask, Device, MockBox, MouseEvent};
use std::time::{Duration, Instant};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let stream = device.catch_events(CatchMask::all()).unwrap();
const TOTAL: u16 = 300;
const KEPT: u16 = 256; for i in 0..TOTAL {
mock.push_event(
(i & 0xff) as u8,
MouseEvent {
buttons: 0,
dx: i as i16, dy: 0,
wheel: 0,
},
);
}
let want_dropped = (TOTAL - KEPT) as u64;
let deadline = Instant::now() + Duration::from_secs(2);
while stream.dropped() < want_dropped && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(10));
}
assert_eq!(
stream.dropped(),
want_dropped,
"exactly the overflow count was dropped"
);
let event = stream
.recv_timeout(Duration::from_secs(1))
.expect("an event survived");
let CatchEvent::Mouse(first) = event else {
panic!("expected a mouse event, got {event:?}");
};
assert_eq!(
first.dx,
(TOTAL - KEPT) as i16,
"the oldest events were dropped, the newest kept"
);
assert!(!first.is_pressed(Button::Left));
}