selection-capture 0.1.5

Sync, cancellable selected-text capture engine with strategy-aware fallbacks
Documentation
use super::*;
use crate::linux_observer::linux_observer_test_lock;
use crate::linux_subscriber::linux_native_subscriber_stats;
use crate::LinuxObserverBridge;

#[derive(Debug)]
struct StubBackend {
    atspi: PlatformAttemptResult,
    x11_selection: PlatformAttemptResult,
    clipboard: PlatformAttemptResult,
}

impl LinuxBackend for StubBackend {
    fn attempt_atspi(&self) -> PlatformAttemptResult {
        self.atspi.clone()
    }

    fn attempt_x11_selection(&self) -> PlatformAttemptResult {
        self.x11_selection.clone()
    }

    fn attempt_clipboard(&self) -> PlatformAttemptResult {
        self.clipboard.clone()
    }
}

#[test]
fn constructor_builds_stub_platform() {
    let platform = LinuxPlatform::new();
    let _ = platform;
}

#[test]
fn selection_monitor_default_poll_interval_is_stable() {
    let monitor = LinuxSelectionMonitor::default();
    assert_eq!(monitor.poll_interval, Duration::from_millis(120));
    assert_eq!(monitor.backend(), LinuxMonitorBackend::Polling);
}

#[test]
fn selection_monitor_emits_only_new_values() {
    let monitor = LinuxSelectionMonitor::new(Duration::from_millis(10));
    assert_eq!(
        monitor.emit_if_new("first".to_string()),
        Some("first".to_string())
    );
    assert_eq!(monitor.emit_if_new("first".to_string()), None);
    assert_eq!(
        monitor.emit_if_new("second".to_string()),
        Some("second".to_string())
    );
}

#[test]
fn selection_monitor_native_preferred_uses_event_pump_when_available() {
    let _guard = linux_observer_test_lock()
        .lock()
        .expect("test lock poisoned");
    fn pump() -> Vec<String> {
        vec![
            "  native a ".to_string(),
            "native a".to_string(),
            "native b".to_string(),
        ]
    }

    let monitor = LinuxSelectionMonitor::new_with_options(LinuxSelectionMonitorOptions {
        poll_interval: Duration::from_millis(10),
        backend: LinuxMonitorBackend::NativeEventPreferred,
        native_queue_capacity: 8,
        native_event_pump: Some(pump),
    });

    assert_eq!(
        monitor.next_selection_change(),
        Some("native a".to_string())
    );
    assert_eq!(
        monitor.next_selection_change(),
        Some("native b".to_string())
    );
}

#[test]
fn selection_monitor_native_preferred_uses_bridge_drain_by_default() {
    let _guard = linux_observer_test_lock()
        .lock()
        .expect("test lock poisoned");
    let _ = LinuxObserverBridge::stop();
    let _ = LinuxObserverBridge::start();
    assert!(LinuxObserverBridge::is_active());
    assert!(LinuxObserverBridge::push_event("bridge one"));
    assert!(LinuxObserverBridge::push_event("bridge two"));

    let monitor = LinuxSelectionMonitor::new_with_options(LinuxSelectionMonitorOptions {
        poll_interval: Duration::from_millis(10),
        backend: LinuxMonitorBackend::NativeEventPreferred,
        native_queue_capacity: 8,
        native_event_pump: None,
    });

    assert_eq!(
        monitor.next_selection_change(),
        Some("bridge one".to_string())
    );
    assert_eq!(
        monitor.next_selection_change(),
        Some("bridge two".to_string())
    );
    assert!(LinuxObserverBridge::is_active());
    let _ = LinuxObserverBridge::stop();
}

#[test]
fn selection_monitor_native_preferred_releases_bridge_on_drop() {
    let _guard = linux_observer_test_lock()
        .lock()
        .expect("test lock poisoned");
    let _ = LinuxObserverBridge::stop();

    {
        let _monitor = LinuxSelectionMonitor::new_with_options(LinuxSelectionMonitorOptions {
            poll_interval: Duration::from_millis(10),
            backend: LinuxMonitorBackend::NativeEventPreferred,
            native_queue_capacity: 8,
            native_event_pump: None,
        });
        assert!(LinuxObserverBridge::is_active());
    }

    assert!(!LinuxObserverBridge::is_active());
}

#[test]
fn selection_monitor_native_preferred_transitions_subscriber_manager_lifecycle() {
    let _guard = linux_observer_test_lock()
        .lock()
        .expect("test lock poisoned");
    let _ = LinuxObserverBridge::stop();
    let before = linux_native_subscriber_stats();

    {
        let _monitor = LinuxSelectionMonitor::new_with_options(LinuxSelectionMonitorOptions {
            poll_interval: Duration::from_millis(10),
            backend: LinuxMonitorBackend::NativeEventPreferred,
            native_queue_capacity: 8,
            native_event_pump: None,
        });
        let during = linux_native_subscriber_stats();
        assert!(during.active);
        assert_eq!(during.starts, before.starts + 1);
    }

    let after = linux_native_subscriber_stats();
    assert!(after.stops > before.stops);
}

#[test]
fn selection_monitor_native_preferred_applies_queue_capacity() {
    let _guard = linux_observer_test_lock()
        .lock()
        .expect("test lock poisoned");
    let monitor = LinuxSelectionMonitor::new_with_options(LinuxSelectionMonitorOptions {
        poll_interval: Duration::from_millis(10),
        backend: LinuxMonitorBackend::NativeEventPreferred,
        native_queue_capacity: 2,
        native_event_pump: None,
    });
    let accepted = monitor.enqueue_native_selection_events(vec![
        "first".to_string(),
        "second".to_string(),
        "third".to_string(),
    ]);
    assert_eq!(accepted, 3);
    assert_eq!(monitor.native_queue_depth(), 2);
    assert_eq!(monitor.native_events_dropped(), 1);
    assert_eq!(monitor.next_selection_change(), Some("second".to_string()));
    assert_eq!(monitor.next_selection_change(), Some("third".to_string()));
}

#[test]
fn active_app_probe_does_not_panic() {
    let platform = LinuxPlatform::new();
    let _ = platform.active_app();
}

#[test]
fn dispatches_primary_accessibility_to_atspi() {
    let backend = StubBackend {
        atspi: PlatformAttemptResult::PermissionDenied,
        x11_selection: PlatformAttemptResult::Unavailable,
        clipboard: PlatformAttemptResult::Unavailable,
    };

    let result = LinuxPlatform::dispatch_attempt(&backend, CaptureMethod::AccessibilityPrimary);

    assert_eq!(result, PlatformAttemptResult::PermissionDenied);
}

#[test]
fn dispatches_range_accessibility_to_x11_selection() {
    let backend = StubBackend {
        atspi: PlatformAttemptResult::Unavailable,
        x11_selection: PlatformAttemptResult::EmptySelection,
        clipboard: PlatformAttemptResult::Unavailable,
    };

    let result = LinuxPlatform::dispatch_attempt(&backend, CaptureMethod::AccessibilityRange);

    assert_eq!(result, PlatformAttemptResult::EmptySelection);
}

#[test]
fn dispatches_clipboard_methods_to_clipboard_attempt() {
    let backend = StubBackend {
        atspi: PlatformAttemptResult::Unavailable,
        x11_selection: PlatformAttemptResult::Unavailable,
        clipboard: PlatformAttemptResult::Success("clipboard".into()),
    };

    assert_eq!(
        LinuxPlatform::dispatch_attempt(&backend, CaptureMethod::ClipboardBorrow),
        PlatformAttemptResult::Success("clipboard".into())
    );
    assert_eq!(
        LinuxPlatform::dispatch_attempt(&backend, CaptureMethod::SyntheticCopy),
        PlatformAttemptResult::Success("clipboard".into())
    );
}

#[test]
fn detects_linux_session_flags_from_env_presence() {
    assert_eq!(
        detect_linux_session(Some("wayland-0"), None),
        LinuxSession {
            wayland: true,
            x11: false,
        }
    );
    assert_eq!(
        detect_linux_session(None, Some(":0")),
        LinuxSession {
            wayland: false,
            x11: true,
        }
    );
    assert_eq!(
        detect_linux_session(Some(""), Some("")),
        LinuxSession {
            wayland: false,
            x11: false,
        }
    );
}

#[test]
fn clipboard_plan_prioritizes_wayland_only_session() {
    let plan = clipboard_command_plan(LinuxSession {
        wayland: true,
        x11: false,
    });
    assert_eq!(plan[0].program, "wl-paste");
    assert_eq!(plan[1].program, "wl-paste");
    assert_eq!(plan[0].args, &["--no-newline", "--type", "text"]);
}

#[test]
fn clipboard_plan_prioritizes_x11_only_session() {
    let plan = clipboard_command_plan(LinuxSession {
        wayland: false,
        x11: true,
    });
    assert_eq!(plan[0].program, "xclip");
    assert_eq!(plan[1].program, "xsel");
    assert_eq!(plan[0].args, &["-o", "-selection", "clipboard"]);
}

#[test]
fn primary_selection_plan_prioritizes_wayland_only_session() {
    let plan = primary_selection_command_plan(LinuxSession {
        wayland: true,
        x11: false,
    });
    assert_eq!(plan[0].program, "wl-paste");
    assert_eq!(plan[1].program, "wl-paste");
    assert_eq!(
        plan[0].args,
        &["--primary", "--no-newline", "--type", "text"]
    );
}