705 lines
25 KiB
Rust
705 lines
25 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn routing_and_view_env_values_are_stable() {
|
|
assert_eq!(InputRouting::Local.as_env(), "0");
|
|
assert_eq!(InputRouting::Remote.as_env(), "1");
|
|
assert_eq!(ViewMode::Unified.as_env(), "unified");
|
|
assert_eq!(ViewMode::Breakout.as_env(), "breakout");
|
|
assert_eq!(DisplaySurface::Preview.label(), "preview");
|
|
assert_eq!(DisplaySurface::Window.label(), "window");
|
|
}
|
|
|
|
#[test]
|
|
fn preset_ids_labels_and_legacy_aliases_are_stable() {
|
|
for (preset, id, label) in [
|
|
(FeedSourcePreset::ThisEye, "self", "Left Eye"),
|
|
(FeedSourcePreset::OtherEye, "other", "Right Eye"),
|
|
(FeedSourcePreset::Off, "off", "Off"),
|
|
] {
|
|
assert_eq!(preset.as_id(), id);
|
|
assert_eq!(FeedSourcePreset::from_id(id), Some(preset));
|
|
assert_eq!(preset.label(0), label);
|
|
}
|
|
assert_eq!(FeedSourcePreset::ThisEye.label(1), "Right Eye");
|
|
assert_eq!(FeedSourcePreset::OtherEye.label(1), "Left Eye");
|
|
assert_eq!(FeedSourcePreset::ThisEye.label(9), "This Eye");
|
|
assert_eq!(FeedSourcePreset::from_id("bogus"), None);
|
|
|
|
for (preset, id, label) in [
|
|
(BreakoutSizePreset::P360, "360p", "360p"),
|
|
(BreakoutSizePreset::P540, "540p", "540p"),
|
|
(BreakoutSizePreset::P720, "720p", "720p"),
|
|
(BreakoutSizePreset::P900, "900p", "900p"),
|
|
(BreakoutSizePreset::P1080, "1080p", "1080p"),
|
|
(BreakoutSizePreset::P1440, "1440p", "1440p"),
|
|
(BreakoutSizePreset::Source, "source", "Source"),
|
|
(BreakoutSizePreset::FillDisplay, "fill", "Display"),
|
|
] {
|
|
assert_eq!(preset.as_id(), id);
|
|
assert_eq!(BreakoutSizePreset::from_id(id), Some(preset));
|
|
assert_eq!(preset.label(), label);
|
|
}
|
|
assert_eq!(BreakoutSizePreset::from_id("giant"), None);
|
|
|
|
for (preset, id, label) in [
|
|
(CaptureSizePreset::Vga, "vga", "VGA"),
|
|
(CaptureSizePreset::P480, "480p", "480p"),
|
|
(CaptureSizePreset::P576, "576p", "576p"),
|
|
(CaptureSizePreset::P720, "720p", "720p"),
|
|
(CaptureSizePreset::P1080, "1080p", "1080p"),
|
|
] {
|
|
assert_eq!(preset.as_id(), id);
|
|
assert_eq!(preset.label(), label);
|
|
assert_eq!(preset.transport_label(), "device H.264 pass-through");
|
|
}
|
|
assert_eq!(
|
|
CaptureSizePreset::from_id("360p"),
|
|
Some(CaptureSizePreset::Vga)
|
|
);
|
|
assert_eq!(
|
|
CaptureSizePreset::from_id("540p"),
|
|
Some(CaptureSizePreset::P480)
|
|
);
|
|
assert_eq!(
|
|
CaptureSizePreset::from_id("900p"),
|
|
Some(CaptureSizePreset::P1080)
|
|
);
|
|
assert_eq!(
|
|
CaptureSizePreset::from_id("source"),
|
|
Some(CaptureSizePreset::P1080)
|
|
);
|
|
assert_eq!(CaptureSizePreset::from_id("unknown"), None);
|
|
assert_eq!(CaptureSizePreset::P720.display_size(), (1280, 720));
|
|
assert!(CaptureSizePreset::P1080.display_aspect_ratio() > 1.7);
|
|
}
|
|
|
|
#[test]
|
|
fn defaults_pick_remote_unified_and_inactive_session() {
|
|
let state = LauncherState::new();
|
|
assert_eq!(state.routing, InputRouting::Remote);
|
|
assert_eq!(state.view_mode, ViewMode::Unified);
|
|
assert_eq!(state.display_surface(0), DisplaySurface::Preview);
|
|
assert_eq!(state.display_surface(1), DisplaySurface::Preview);
|
|
assert_eq!(state.preview_source_size(), PreviewSourceSize::default());
|
|
assert_eq!(state.breakout_limit_size(), PreviewSourceSize::default());
|
|
assert_eq!(state.capture_size_preset(0), CaptureSizePreset::P1080);
|
|
assert_eq!(state.breakout_size_preset(0), BreakoutSizePreset::Source);
|
|
assert!(!state.server_available);
|
|
assert!(!state.remote_active);
|
|
assert!(state.devices.camera.is_none());
|
|
assert!(state.devices.microphone.is_none());
|
|
assert!(state.devices.speaker.is_none());
|
|
assert!(state.devices.keyboard.is_none());
|
|
assert!(state.devices.mouse.is_none());
|
|
assert!(!state.channels.camera);
|
|
assert!(!state.channels.microphone);
|
|
assert!(state.channels.audio);
|
|
assert_eq!(state.audio_gain_percent, DEFAULT_AUDIO_GAIN_PERCENT);
|
|
assert_eq!(state.audio_gain_env_value(), "2.000");
|
|
assert_eq!(state.audio_gain_label(), "200%");
|
|
assert_eq!(state.mic_gain_percent, DEFAULT_MIC_GAIN_PERCENT);
|
|
assert_eq!(state.mic_gain_env_value(), "1.000");
|
|
assert_eq!(state.mic_gain_label(), "100%");
|
|
assert_eq!(state.capture_power.unit, "relay.service");
|
|
assert_eq!(state.capture_power.mode, "auto");
|
|
}
|
|
|
|
#[test]
|
|
fn display_surface_updates_global_view_summary() {
|
|
let mut state = LauncherState::new();
|
|
state.set_display_surface(1, DisplaySurface::Window);
|
|
assert_eq!(state.view_mode, ViewMode::Breakout);
|
|
assert_eq!(state.breakout_count(), 1);
|
|
|
|
state.set_display_surface(1, DisplaySurface::Preview);
|
|
assert_eq!(state.view_mode, ViewMode::Unified);
|
|
assert_eq!(state.breakout_count(), 0);
|
|
|
|
state.set_view_mode(ViewMode::Breakout);
|
|
assert_eq!(state.display_surface(0), DisplaySurface::Window);
|
|
assert_eq!(state.display_surface(1), DisplaySurface::Window);
|
|
|
|
state.set_display_surface(9, DisplaySurface::Window);
|
|
assert_eq!(state.display_surface(9), DisplaySurface::Preview);
|
|
assert_eq!(state.breakout_count(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn feed_sources_can_mirror_or_disable_a_pane() {
|
|
let mut state = LauncherState::new();
|
|
state.set_capture_size_preset(1, CaptureSizePreset::P1080);
|
|
|
|
assert_eq!(state.resolved_feed_monitor_id(0), Some(0));
|
|
assert_eq!(state.resolved_feed_monitor_id(1), Some(1));
|
|
|
|
state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);
|
|
assert_eq!(state.resolved_feed_monitor_id(0), Some(1));
|
|
assert_eq!(
|
|
state.display_capture_size_choice(0),
|
|
Some(state.capture_size_choice(1))
|
|
);
|
|
|
|
state.set_feed_source_preset(1, FeedSourcePreset::OtherEye);
|
|
assert_eq!(state.resolved_feed_monitor_id(1), Some(0));
|
|
|
|
state.set_feed_source_preset(0, FeedSourcePreset::Off);
|
|
assert_eq!(state.resolved_feed_monitor_id(0), None);
|
|
assert!(state.display_capture_size_choice(0).is_none());
|
|
|
|
state.set_feed_source_preset(9, FeedSourcePreset::Off);
|
|
assert_eq!(state.feed_source_preset(9), FeedSourcePreset::ThisEye);
|
|
let labels: Vec<_> = state
|
|
.feed_source_options(1)
|
|
.into_iter()
|
|
.map(|choice| (choice.preset, choice.label))
|
|
.collect();
|
|
assert_eq!(
|
|
labels,
|
|
vec![
|
|
(FeedSourcePreset::ThisEye, "Right Eye"),
|
|
(FeedSourcePreset::OtherEye, "Left Eye"),
|
|
(FeedSourcePreset::Off, "Off"),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mirrored_panes_use_their_effective_source_size_for_breakout_source_labels() {
|
|
let mut state = LauncherState::new();
|
|
state.set_capture_size_preset(1, CaptureSizePreset::P720);
|
|
state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);
|
|
|
|
let mirrored_source = state.effective_preview_source_size(0);
|
|
assert_eq!(mirrored_source.width, 1280);
|
|
assert_eq!(mirrored_source.height, 720);
|
|
assert_eq!(mirrored_source.fps, 60);
|
|
|
|
let mirrored_breakout = state.breakout_size_choice(0);
|
|
assert_eq!(mirrored_breakout.preset, BreakoutSizePreset::Source);
|
|
assert_eq!(mirrored_breakout.width, 1280);
|
|
assert_eq!(mirrored_breakout.height, 720);
|
|
|
|
assert_eq!(
|
|
state.display_capture_size_preset(0),
|
|
Some(CaptureSizePreset::P720)
|
|
);
|
|
assert_eq!(state.display_capture_fps(0), Some(60));
|
|
assert_eq!(state.display_capture_bitrate_kbit(0), Some(12_000));
|
|
}
|
|
|
|
#[test]
|
|
fn zero_and_out_of_range_profile_updates_are_safe_noops() {
|
|
let mut state = LauncherState::new();
|
|
let original_source = state.preview_source_size();
|
|
state.set_preview_source_profile(0, 1080, 0);
|
|
assert_eq!(state.preview_source_size(), original_source);
|
|
|
|
let original_limit = state.breakout_limit_size();
|
|
state.set_breakout_limit_size(1920, 0);
|
|
assert_eq!(state.breakout_limit_size(), original_limit);
|
|
|
|
let original_display = state.breakout_display_size();
|
|
state.set_breakout_display_size(0, 1080);
|
|
assert_eq!(state.breakout_display_size(), original_display);
|
|
|
|
state.set_breakout_display_size(3440, 1440);
|
|
assert_eq!(state.breakout_display_size().width, 3440);
|
|
assert_eq!(state.breakout_display_size().height, 1440);
|
|
|
|
state.set_capture_size_preset(9, CaptureSizePreset::P720);
|
|
assert_eq!(state.capture_size_preset(9), CaptureSizePreset::P1080);
|
|
state.set_capture_fps(9, 0);
|
|
assert_eq!(state.capture_fps(9), default_eye_source_mode().fps);
|
|
state.set_capture_bitrate_kbit(9, 1);
|
|
assert!(state.capture_bitrate_kbit(9) >= 18_000);
|
|
state.set_breakout_size_preset(9, BreakoutSizePreset::P360);
|
|
assert_eq!(state.breakout_size_preset(9), BreakoutSizePreset::Source);
|
|
}
|
|
|
|
#[test]
|
|
fn selecting_auto_or_blank_clears_explicit_device() {
|
|
let mut state = LauncherState::new();
|
|
state.select_camera(Some("/dev/video0".to_string()));
|
|
assert_eq!(state.devices.camera.as_deref(), Some("/dev/video0"));
|
|
|
|
state.select_camera(Some("auto".to_string()));
|
|
assert!(state.devices.camera.is_none());
|
|
|
|
state.select_microphone(Some(" ".to_string()));
|
|
assert!(state.devices.microphone.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn catalog_defaults_stage_real_media_devices_without_enabling_channels() {
|
|
let mut state = LauncherState::new();
|
|
state.select_camera(Some("/dev/video-special".to_string()));
|
|
|
|
let catalog = DeviceCatalog {
|
|
cameras: vec!["/dev/video0".to_string()],
|
|
camera_modes: [(
|
|
"/dev/video0".to_string(),
|
|
vec![CameraMode::new(1920, 1080, 30)],
|
|
)]
|
|
.into_iter()
|
|
.collect(),
|
|
microphones: vec!["alsa_input.usb".to_string()],
|
|
speakers: vec!["alsa_output.usb".to_string()],
|
|
keyboards: vec!["/dev/input/event10".to_string()],
|
|
mice: vec!["/dev/input/event11".to_string()],
|
|
};
|
|
|
|
state.apply_catalog_defaults(&catalog);
|
|
|
|
assert_eq!(state.devices.camera.as_deref(), Some("/dev/video-special"));
|
|
assert_eq!(state.devices.microphone.as_deref(), Some("alsa_input.usb"));
|
|
assert_eq!(state.devices.speaker.as_deref(), Some("alsa_output.usb"));
|
|
assert!(!state.channels.camera);
|
|
assert!(!state.channels.microphone);
|
|
assert!(state.channels.audio);
|
|
|
|
let mut fresh = LauncherState::new();
|
|
fresh.apply_catalog_defaults(&catalog);
|
|
assert_eq!(fresh.devices.camera.as_deref(), Some("/dev/video0"));
|
|
assert_eq!(fresh.camera_quality, Some(CameraMode::new(1920, 1080, 30)));
|
|
assert_eq!(fresh.devices.microphone.as_deref(), Some("alsa_input.usb"));
|
|
assert_eq!(fresh.devices.speaker.as_deref(), Some("alsa_output.usb"));
|
|
}
|
|
|
|
#[test]
|
|
fn camera_quality_tracks_selected_camera_supported_modes() {
|
|
let catalog = DeviceCatalog {
|
|
cameras: vec!["cam-a".to_string(), "cam-b".to_string()],
|
|
camera_modes: [
|
|
(
|
|
"cam-a".to_string(),
|
|
vec![
|
|
CameraMode::new(1920, 1080, 30),
|
|
CameraMode::new(1920, 1080, 20),
|
|
CameraMode::new(1280, 720, 30),
|
|
CameraMode::new(1280, 720, 20),
|
|
],
|
|
),
|
|
("cam-b".to_string(), vec![CameraMode::new(1280, 720, 30)]),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
..DeviceCatalog::default()
|
|
};
|
|
|
|
let mut state = LauncherState::new();
|
|
state.apply_catalog_defaults(&catalog);
|
|
assert_eq!(state.devices.camera.as_deref(), Some("cam-a"));
|
|
assert_eq!(
|
|
state.camera_quality_options(&catalog),
|
|
vec![
|
|
CameraMode::new(1920, 1080, 30),
|
|
CameraMode::new(1920, 1080, 20),
|
|
CameraMode::new(1280, 720, 30),
|
|
CameraMode::new(1280, 720, 20)
|
|
]
|
|
);
|
|
|
|
state.select_camera_quality(Some(CameraMode::new(1280, 720, 30)));
|
|
assert_eq!(
|
|
state.selected_camera_quality(&catalog),
|
|
Some(CameraMode::new(1280, 720, 30))
|
|
);
|
|
|
|
state.select_camera(Some("cam-b".to_string()));
|
|
state.normalize_camera_quality(&catalog);
|
|
assert_eq!(state.camera_quality, Some(CameraMode::new(1280, 720, 30)));
|
|
|
|
state.select_camera(None);
|
|
state.normalize_camera_quality(&catalog);
|
|
assert_eq!(state.camera_quality, None);
|
|
}
|
|
|
|
#[test]
|
|
fn audio_gain_is_clamped_and_formatted_for_launcher_and_runtime() {
|
|
let mut state = LauncherState::new();
|
|
state.set_audio_gain_percent(350);
|
|
assert_eq!(state.audio_gain_percent, 350);
|
|
assert_eq!(state.audio_gain_label(), "350%");
|
|
assert_eq!(state.audio_gain_env_value(), "3.500");
|
|
|
|
state.set_audio_gain_percent(10_000);
|
|
assert_eq!(state.audio_gain_percent, MAX_AUDIO_GAIN_PERCENT);
|
|
assert_eq!(state.audio_gain_label(), "800%");
|
|
assert_eq!(state.audio_gain_env_value(), "8.000");
|
|
|
|
state.set_mic_gain_percent(325);
|
|
assert_eq!(state.mic_gain_percent, 325);
|
|
assert_eq!(state.mic_gain_label(), "325%");
|
|
assert_eq!(state.mic_gain_env_value(), "3.250");
|
|
|
|
state.set_mic_gain_percent(10_000);
|
|
assert_eq!(state.mic_gain_percent, MAX_MIC_GAIN_PERCENT);
|
|
assert_eq!(state.mic_gain_label(), "400%");
|
|
assert_eq!(state.mic_gain_env_value(), "4.000");
|
|
}
|
|
|
|
#[test]
|
|
fn start_and_stop_remote_only_report_changes_once() {
|
|
let mut state = LauncherState::new();
|
|
assert!(state.start_remote());
|
|
assert!(!state.start_remote());
|
|
assert!(state.remote_active);
|
|
|
|
assert!(state.stop_remote());
|
|
assert!(!state.stop_remote());
|
|
assert!(!state.remote_active);
|
|
}
|
|
|
|
#[test]
|
|
fn status_line_mentions_all_user_visible_controls() {
|
|
let mut state = LauncherState::new();
|
|
state.set_server_available(true);
|
|
state.set_routing(InputRouting::Local);
|
|
state.set_view_mode(ViewMode::Unified);
|
|
state.select_camera(Some("/dev/video0".to_string()));
|
|
state.select_camera_quality(Some(CameraMode::new(1920, 1080, 30)));
|
|
state.select_microphone(Some("alsa_input.usb".to_string()));
|
|
state.select_speaker(Some("alsa_output.usb".to_string()));
|
|
state.set_camera_channel_enabled(true);
|
|
state.set_microphone_channel_enabled(true);
|
|
state.set_audio_channel_enabled(true);
|
|
state.select_keyboard(Some("/dev/input/event-kbd".to_string()));
|
|
state.select_mouse(Some("/dev/input/event-mouse".to_string()));
|
|
state.set_preview_source_profile(1920, 1080, 30);
|
|
state.start_remote();
|
|
|
|
let status = state.status_line();
|
|
assert!(status.contains("mode=local"));
|
|
assert!(status.contains("server=true"));
|
|
assert!(status.contains("view=unified"));
|
|
assert!(status.contains("active=true"));
|
|
assert!(status.contains("source=1920x1080"));
|
|
assert!(status.contains("d1=preview"));
|
|
assert!(status.contains("d2=preview"));
|
|
assert!(status.contains("camera=/dev/video0"));
|
|
assert!(status.contains("camera_quality=1080p@30"));
|
|
assert!(status.contains("mic=alsa_input.usb"));
|
|
assert!(status.contains("speaker=alsa_output.usb"));
|
|
assert!(status.contains("audio_gain=200%"));
|
|
assert!(status.contains("kbd=/dev/input/event-kbd"));
|
|
assert!(status.contains("mouse=/dev/input/event-mouse"));
|
|
}
|
|
|
|
#[test]
|
|
fn capture_power_status_updates_snapshot_state() {
|
|
let mut state = LauncherState::new();
|
|
state.set_capture_power(CapturePowerStatus {
|
|
available: true,
|
|
enabled: true,
|
|
unit: "relay.service".to_string(),
|
|
detail: "active/running".to_string(),
|
|
active_leases: 2,
|
|
mode: "forced-on".to_string(),
|
|
detected_devices: 2,
|
|
});
|
|
|
|
assert!(state.capture_power.available);
|
|
assert!(state.capture_power.enabled);
|
|
assert_eq!(state.capture_power.active_leases, 2);
|
|
assert!(state.status_line().contains("power=on"));
|
|
}
|
|
|
|
#[test]
|
|
fn calibration_status_tracks_proto_unavailable_and_status_line() {
|
|
let mut state = LauncherState::new();
|
|
assert!(!state.calibration.available);
|
|
assert_eq!(state.calibration.active_audio_offset_us, 0);
|
|
|
|
let unavailable = CalibrationStatus::unavailable("server unreachable");
|
|
assert!(!unavailable.available);
|
|
assert_eq!(unavailable.detail, "server unreachable");
|
|
|
|
state.set_calibration(CalibrationStatus::from_proto(
|
|
lesavka_common::lesavka::CalibrationState {
|
|
profile: "mjpeg".to_string(),
|
|
factory_audio_offset_us: 0,
|
|
factory_video_offset_us: 0,
|
|
default_audio_offset_us: -40_000,
|
|
default_video_offset_us: 1_000,
|
|
active_audio_offset_us: -35_000,
|
|
active_video_offset_us: 2_000,
|
|
source: "manual".to_string(),
|
|
confidence: "measured".to_string(),
|
|
updated_at: "now".to_string(),
|
|
detail: "operator-set".to_string(),
|
|
},
|
|
));
|
|
|
|
assert!(state.calibration.available);
|
|
assert_eq!(state.calibration.profile, "mjpeg");
|
|
assert_eq!(state.calibration.factory_audio_offset_us, 0);
|
|
assert_eq!(state.calibration.factory_video_offset_us, 0);
|
|
assert_eq!(state.calibration.default_audio_offset_us, -40_000);
|
|
assert_eq!(state.calibration.default_video_offset_us, 1_000);
|
|
assert_eq!(state.calibration.active_audio_offset_us, -35_000);
|
|
assert_eq!(state.calibration.active_video_offset_us, 2_000);
|
|
assert_eq!(state.calibration.source, "manual");
|
|
assert_eq!(state.calibration.confidence, "measured");
|
|
assert_eq!(state.calibration.updated_at, "now");
|
|
assert_eq!(state.calibration.detail, "operator-set");
|
|
assert!(state.status_line().contains("cal=manual:-35.0ms"));
|
|
}
|
|
|
|
#[test]
|
|
fn server_availability_tracks_reachability() {
|
|
let mut state = LauncherState::new();
|
|
assert!(!state.server_available);
|
|
state.set_server_available(true);
|
|
assert!(state.server_available);
|
|
}
|
|
|
|
#[test]
|
|
fn server_identity_and_media_caps_trim_blank_values() {
|
|
let mut state = LauncherState::new();
|
|
state.set_server_version(Some(" ".to_string()));
|
|
assert_eq!(state.server_version, None);
|
|
|
|
state.set_server_version(Some(" 0.16.0 ".to_string()));
|
|
assert_eq!(state.server_version.as_deref(), Some("0.16.0"));
|
|
|
|
state.set_server_media_caps(
|
|
Some(true),
|
|
Some(false),
|
|
Some(" ".to_string()),
|
|
Some(" mjpeg ".to_string()),
|
|
);
|
|
assert_eq!(state.server_camera, Some(true));
|
|
assert_eq!(state.server_microphone, Some(false));
|
|
assert_eq!(state.server_camera_output, None);
|
|
assert_eq!(state.server_camera_codec.as_deref(), Some("mjpeg"));
|
|
|
|
state.set_server_media_caps(
|
|
None,
|
|
None,
|
|
Some(" uvc ".to_string()),
|
|
Some(" ".to_string()),
|
|
);
|
|
assert_eq!(state.server_camera, None);
|
|
assert_eq!(state.server_microphone, None);
|
|
assert_eq!(state.server_camera_output.as_deref(), Some("uvc"));
|
|
assert_eq!(state.server_camera_codec, None);
|
|
}
|
|
|
|
#[test]
|
|
fn breakout_size_choices_track_the_negotiated_source_size() {
|
|
let mut state = LauncherState::new();
|
|
state.set_preview_source_profile(1920, 1080, 60);
|
|
state.set_breakout_limit_size(2560, 1440);
|
|
|
|
let source = state.capture_size_choice(0);
|
|
assert_eq!(source.width, 1920);
|
|
assert_eq!(source.height, 1080);
|
|
assert_eq!(source.fps, 60);
|
|
assert_eq!(source.max_bitrate_kbit, 18_000);
|
|
|
|
state.set_capture_size_preset(0, CaptureSizePreset::P480);
|
|
let compact_capture = state.capture_size_choice(0);
|
|
assert_eq!(compact_capture.preset, CaptureSizePreset::P720);
|
|
assert_eq!(compact_capture.width, 1280);
|
|
assert_eq!(compact_capture.height, 720);
|
|
assert_eq!(compact_capture.fps, 60);
|
|
assert_eq!(compact_capture.max_bitrate_kbit, 12_000);
|
|
|
|
let effective_source = state.effective_preview_source_size(0);
|
|
assert_eq!(effective_source.width, 1280);
|
|
assert_eq!(effective_source.height, 720);
|
|
assert_eq!(effective_source.fps, 60);
|
|
|
|
let display = state.breakout_size_choice(0);
|
|
assert_eq!(display.width, 1280);
|
|
assert_eq!(display.height, 720);
|
|
|
|
state.set_breakout_size_preset(0, BreakoutSizePreset::P360);
|
|
let smaller = state.breakout_size_choice(0);
|
|
assert_eq!(smaller.width, 640);
|
|
assert_eq!(smaller.height, 360);
|
|
|
|
state.set_breakout_size_preset(0, BreakoutSizePreset::P540);
|
|
let compact = state.breakout_size_choice(0);
|
|
assert_eq!(compact.width, 960);
|
|
assert_eq!(compact.height, 540);
|
|
|
|
let capture_options = state.capture_size_options();
|
|
assert_eq!(capture_options.len(), 2);
|
|
assert_eq!(capture_options[0].preset, CaptureSizePreset::P1080);
|
|
assert_eq!(capture_options[0].width, 1920);
|
|
assert_eq!(capture_options[0].height, 1080);
|
|
assert_eq!(capture_options[0].fps, 60);
|
|
assert_eq!(capture_options[0].max_bitrate_kbit, 18_000);
|
|
|
|
let breakout_options = state.breakout_size_options(0);
|
|
assert!(breakout_options.len() >= 5);
|
|
assert!(breakout_options.iter().any(|choice| {
|
|
choice.preset == BreakoutSizePreset::Source && choice.width == 1280 && choice.height == 720
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn capture_option_methods_report_native_timing_and_bitrate_tiers() {
|
|
let mut state = LauncherState::new();
|
|
state.set_preview_source_profile(1920, 1080, 30);
|
|
|
|
let fps_options = state.capture_fps_options();
|
|
assert_eq!(fps_options.len(), 1);
|
|
assert_eq!(fps_options[0].fps, 30);
|
|
|
|
let bitrate_options = state.capture_bitrate_options();
|
|
assert_eq!(bitrate_options.len(), 1);
|
|
assert_eq!(bitrate_options[0].max_bitrate_kbit, 12_000);
|
|
}
|
|
|
|
#[test]
|
|
fn swap_key_binding_tracks_selected_key_and_binding_mode() {
|
|
let mut state = LauncherState::new();
|
|
assert_eq!(state.swap_key, "pause");
|
|
assert!(!state.swap_key_binding);
|
|
|
|
let token = state.begin_swap_key_binding();
|
|
assert!(state.swap_key_binding);
|
|
assert_eq!(token, state.swap_key_binding_token);
|
|
|
|
state.set_swap_key("F8");
|
|
assert_eq!(state.swap_key, "f8");
|
|
|
|
state.set_swap_key(" ");
|
|
assert_eq!(state.swap_key, "off");
|
|
|
|
state.finish_swap_key_binding();
|
|
assert!(!state.swap_key_binding);
|
|
}
|
|
|
|
#[test]
|
|
fn swap_key_binding_timeout_only_cancels_matching_attempt() {
|
|
let mut state = LauncherState::new();
|
|
let first = state.begin_swap_key_binding();
|
|
let second = state.begin_swap_key_binding();
|
|
|
|
assert!(!state.cancel_swap_key_binding(first));
|
|
assert!(state.swap_key_binding);
|
|
assert!(state.cancel_swap_key_binding(second));
|
|
assert!(!state.swap_key_binding);
|
|
}
|
|
|
|
#[test]
|
|
fn complete_swap_key_binding_updates_value_and_ends_binding() {
|
|
let mut state = LauncherState::new();
|
|
state.begin_swap_key_binding();
|
|
|
|
state.complete_swap_key_binding("F12");
|
|
|
|
assert_eq!(state.swap_key, "f12");
|
|
assert!(!state.swap_key_binding);
|
|
}
|
|
|
|
#[test]
|
|
fn push_note_accumulates_operator_context() {
|
|
let mut state = LauncherState::new();
|
|
state.push_note("preview warm");
|
|
state.push_note("relay linked");
|
|
|
|
assert_eq!(state.notes, vec!["preview warm", "relay linked"]);
|
|
}
|
|
|
|
#[test]
|
|
fn capture_size_presets_map_to_real_device_modes() {
|
|
let mut state = LauncherState::new();
|
|
state.set_preview_source_profile(1920, 1080, 60);
|
|
state.set_capture_size_preset(0, CaptureSizePreset::P1080);
|
|
let source = state.capture_size_choice(0);
|
|
assert_eq!(source.width, 1920);
|
|
assert_eq!(source.height, 1080);
|
|
assert_eq!(source.fps, 60);
|
|
assert!(source.max_bitrate_kbit >= 18_000);
|
|
|
|
state.set_capture_size_preset(0, CaptureSizePreset::P720);
|
|
let hd = state.capture_size_choice(0);
|
|
assert_eq!(hd.preset, CaptureSizePreset::P720);
|
|
assert_eq!(hd.width, 1280);
|
|
assert_eq!(hd.height, 720);
|
|
assert_eq!(hd.fps, 60);
|
|
|
|
state.set_capture_size_preset(0, CaptureSizePreset::P576);
|
|
let compact = state.capture_size_choice(0);
|
|
assert_eq!(compact.preset, CaptureSizePreset::P720);
|
|
assert_eq!(compact.width, 1280);
|
|
assert_eq!(compact.height, 720);
|
|
assert_eq!(compact.fps, 60);
|
|
|
|
state.set_capture_size_preset(0, CaptureSizePreset::Vga);
|
|
let small = state.capture_size_choice(0);
|
|
assert_eq!(small.preset, CaptureSizePreset::P720);
|
|
assert_eq!(small.width, 1280);
|
|
assert_eq!(small.height, 720);
|
|
assert_eq!(small.fps, 60);
|
|
}
|
|
|
|
#[test]
|
|
fn source_capture_knobs_follow_the_selected_native_mode() {
|
|
let mut state = LauncherState::new();
|
|
state.set_preview_source_profile(1920, 1080, 60);
|
|
|
|
state.set_capture_size_preset(1, CaptureSizePreset::P1080);
|
|
let defaults = state.capture_size_choice(1);
|
|
assert_eq!(defaults.width, 1920);
|
|
assert_eq!(defaults.height, 1080);
|
|
assert_eq!(defaults.fps, 60);
|
|
assert_eq!(defaults.max_bitrate_kbit, 18_000);
|
|
|
|
state.set_capture_fps(1, 24);
|
|
state.set_capture_bitrate_kbit(1, 8_500);
|
|
let tuned = state.capture_size_choice(1);
|
|
assert_eq!(tuned.preset, CaptureSizePreset::P1080);
|
|
assert_eq!(tuned.width, 1920);
|
|
assert_eq!(tuned.height, 1080);
|
|
assert_eq!(tuned.fps, 60);
|
|
assert_eq!(tuned.max_bitrate_kbit, 18_000);
|
|
}
|
|
|
|
#[test]
|
|
fn capture_option_helpers_report_native_fps_and_bitrate_tiers() {
|
|
let full_hd = PreviewSourceSize {
|
|
width: 1920,
|
|
height: 1080,
|
|
fps: 30,
|
|
};
|
|
assert_eq!(capture_fps_options(full_hd)[0].fps, 30);
|
|
assert_eq!(capture_bitrate_options(full_hd)[0].max_bitrate_kbit, 12_000);
|
|
assert_eq!(estimate_source_bitrate_kbit(1280, 720, 30), 6_000);
|
|
assert_eq!(estimate_source_bitrate_kbit(640, 480, 15), 2_500);
|
|
|
|
let compact = PreviewSourceSize {
|
|
width: 640,
|
|
height: 480,
|
|
fps: 0,
|
|
};
|
|
assert_eq!(capture_fps_options(compact)[0].fps, 1);
|
|
assert_eq!(capture_bitrate_options(compact)[0].max_bitrate_kbit, 2_500);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_fit_preserves_aspect_when_width_is_limiting() {
|
|
assert_eq!(fit_standard_dimensions(1000, 2000, 1920, 1080), (1000, 562));
|
|
}
|
|
|
|
#[test]
|
|
fn source_capture_ignores_manual_fps_and_bitrate_knobs() {
|
|
let mut state = LauncherState::new();
|
|
state.set_preview_source_profile(1920, 1080, 60);
|
|
state.set_capture_size_preset(0, CaptureSizePreset::P720);
|
|
state.set_capture_fps(0, 60);
|
|
state.set_capture_bitrate_kbit(0, 24_000);
|
|
|
|
let source = state.capture_size_choice(0);
|
|
assert_eq!(source.preset, CaptureSizePreset::P720);
|
|
assert_eq!(source.width, 1280);
|
|
assert_eq!(source.height, 720);
|
|
assert_eq!(source.fps, 60);
|
|
assert_eq!(source.max_bitrate_kbit, 12_000);
|
|
}
|