test(lesavka): cover camera and keyboard helpers

This commit is contained in:
Brad Stein 2026-05-19 10:23:20 -03:00
parent ceb20abeba
commit 8f911da06b
2 changed files with 147 additions and 1 deletions

View File

@ -247,4 +247,45 @@ mod tests {
("x265enc", Some("key-int-max"))
);
}
#[cfg(coverage)]
#[test]
#[serial]
/// Coverage builds can exercise H.264 encoder branches without host GPU assumptions.
fn coverage_h264_encoder_choice_honors_stable_test_overrides() {
let cases = [
("nvh264enc", ("nvh264enc", None)),
("vulkanh264enc", ("vulkanh264enc", Some("idr-period"))),
("vaapih264enc", ("vaapih264enc", Some("keyframe-period"))),
("v4l2h264enc", ("v4l2h264enc", Some("idrcount"))),
("unknown", ("x264enc", Some("key-int-max"))),
];
for (override_value, expected) in cases {
temp_env::with_var("LESAVKA_CAM_TEST_ENCODER", Some(override_value), || {
assert_eq!(CameraCapture::choose_encoder().unwrap(), expected);
});
}
}
#[cfg(coverage)]
#[test]
/// Coverage mode keeps software video fallback enabled for deterministic tests.
fn coverage_software_video_fallback_is_enabled() {
assert!(CameraCapture::software_video_fallback_allowed());
}
#[cfg(coverage)]
#[test]
/// Coverage mode replaces the FFmpeg preview reader with an inert cancellation flag.
fn coverage_ffmpeg_preview_tap_stub_starts_stopped() {
let running = super::spawn_ffmpeg_raw_preview_tap(
std::io::empty(),
std::path::PathBuf::from("/tmp/lesavka-preview-tap.coverage"),
1,
1,
);
assert!(!running.load(std::sync::atomic::Ordering::Acquire));
}
}

View File

@ -1,5 +1,14 @@
use super::{is_paste_modifier, paste_key_available_from_env, paste_rpc_enabled};
use super::{
build_keyboard_report, debounce_gate, is_modifier, is_paste_modifier, keycode_to_usage,
live_modifier_delay, modifier_only_report, paste_key_available_from_env, paste_rpc_enabled,
read_clipboard_text, should_stage_modifier_report, update_pressed_keys,
};
use evdev::KeyCode;
use std::{
collections::HashSet,
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use tempfile::tempdir;
#[test]
@ -47,3 +56,99 @@ fn paste_modifier_recognizes_ctrl_alt_only() {
assert!(!is_paste_modifier(KeyCode::KEY_V));
assert!(!is_paste_modifier(KeyCode::KEY_LEFTSHIFT));
}
#[test]
fn keyboard_report_sorts_deduplicates_and_keeps_modifiers() {
let report = build_keyboard_report([
KeyCode::KEY_B,
KeyCode::KEY_LEFTSHIFT,
KeyCode::KEY_A,
KeyCode::KEY_A,
KeyCode::KEY_RIGHTCTRL,
]);
assert_eq!(
report[0],
is_modifier(KeyCode::KEY_LEFTSHIFT).unwrap() | is_modifier(KeyCode::KEY_RIGHTCTRL).unwrap()
);
assert_eq!(report[2], keycode_to_usage(KeyCode::KEY_A).unwrap());
assert_eq!(report[3], keycode_to_usage(KeyCode::KEY_B).unwrap());
assert_eq!(&report[4..], &[0, 0, 0, 0]);
}
#[test]
fn modifier_staging_requires_modified_non_modifier_keydown() {
let mut report = [0; 8];
report[0] = is_modifier(KeyCode::KEY_LEFTCTRL).unwrap();
report[2] = keycode_to_usage(KeyCode::KEY_V).unwrap();
assert!(should_stage_modifier_report(KeyCode::KEY_V, 1, report));
assert!(!should_stage_modifier_report(KeyCode::KEY_V, 0, report));
assert!(!should_stage_modifier_report(
KeyCode::KEY_LEFTCTRL,
1,
report
));
let mut unmodified = report;
unmodified[0] = 0;
assert!(!should_stage_modifier_report(KeyCode::KEY_V, 1, unmodified));
}
#[test]
fn modifier_only_report_clears_non_modifier_slots() {
assert_eq!(
modifier_only_report(is_modifier(KeyCode::KEY_LEFTALT).unwrap()),
[
is_modifier(KeyCode::KEY_LEFTALT).unwrap(),
0,
0,
0,
0,
0,
0,
0
]
);
}
#[test]
fn live_modifier_delay_honors_zero_override() {
temp_env::with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
assert_eq!(live_modifier_delay(), Duration::ZERO);
});
}
#[test]
fn clipboard_reader_uses_successful_command_output() {
temp_env::with_var(
"LESAVKA_CLIPBOARD_CMD",
Some("printf clipboard-data"),
|| {
assert_eq!(read_clipboard_text().as_deref(), Some("clipboard-data"));
},
);
}
#[test]
fn debounce_gate_updates_timestamp_even_when_blocked() {
let last = AtomicU64::new(0);
assert!(debounce_gate(&last, 1_000, 250));
assert_eq!(last.load(Ordering::Relaxed), 1_000);
assert!(!debounce_gate(&last, 1_100, 250));
assert_eq!(last.load(Ordering::Relaxed), 1_100);
assert!(debounce_gate(&last, 1_400, 250));
}
#[test]
fn pressed_key_shadow_tracks_press_release_only() {
let mut pressed = HashSet::new();
update_pressed_keys(&mut pressed, KeyCode::KEY_A, 1);
assert!(pressed.contains(&KeyCode::KEY_A));
update_pressed_keys(&mut pressed, KeyCode::KEY_B, -1);
assert!(!pressed.contains(&KeyCode::KEY_B));
update_pressed_keys(&mut pressed, KeyCode::KEY_A, 0);
assert!(!pressed.contains(&KeyCode::KEY_A));
}