#![cfg(feature = "macos_14_0")]
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
#[test]
fn test_picker_configuration_new() {
let config = SCContentSharingPickerConfiguration::new();
assert!(!config.as_ptr().is_null());
println!("✓ Picker configuration created");
}
#[test]
fn test_picker_configuration_default() {
let config = SCContentSharingPickerConfiguration::default();
assert!(!config.as_ptr().is_null());
println!("✓ Picker default configuration created");
}
#[test]
fn test_picker_configuration_default_from_system() {
let config = SCContentSharingPickerConfiguration::default_from_system();
assert!(
!config.as_ptr().is_null(),
"default_from_system() returned a null configuration pointer"
);
let cloned = config.clone();
assert!(!cloned.as_ptr().is_null());
drop(cloned);
let mut config = config;
config.set_excluded_bundle_ids(&["com.apple.dock"]);
drop(config);
}
#[test]
fn test_picker_configuration_clone() {
let config1 = SCContentSharingPickerConfiguration::new();
let config2 = config1.clone();
assert!(!config1.as_ptr().is_null());
assert!(!config2.as_ptr().is_null());
println!("✓ Picker configuration clone works");
}
#[test]
fn test_picker_configuration_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<SCContentSharingPickerConfiguration>();
assert_sync::<SCContentSharingPickerConfiguration>();
println!("✓ SCContentSharingPickerConfiguration is Send + Sync");
}
#[test]
fn test_picker_configuration_lifecycle() {
for i in 0..3 {
let config = SCContentSharingPickerConfiguration::new();
assert!(!config.as_ptr().is_null());
println!("✓ Configuration {i} created");
drop(config);
println!("✓ Configuration {i} dropped");
}
}
#[test]
fn test_picker_configuration_modes() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerMode;
let mut config = SCContentSharingPickerConfiguration::new();
let modes = [
SCContentSharingPickerMode::SingleWindow,
SCContentSharingPickerMode::SingleDisplay,
];
config.set_allowed_picker_modes(&modes);
assert_eq!(config.allowed_picker_modes(), modes);
assert!(!config.as_ptr().is_null());
println!("✓ Picker modes round-trip successfully");
}
#[test]
fn test_picker_api_availability() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _picker_type = std::any::type_name::<SCContentSharingPicker>();
let _config_type = std::any::type_name::<SCContentSharingPickerConfiguration>();
println!("✓ Content sharing picker API available on macOS 14.0+");
}
#[test]
#[cfg(feature = "async")]
fn test_async_picker_types_exist() {
use screencapturekit::async_api::{
AsyncPickerFilterFuture, AsyncPickerFuture, AsyncSCContentSharingPicker,
};
let _picker_type = std::any::type_name::<AsyncSCContentSharingPicker>();
let _future_type = std::any::type_name::<AsyncPickerFuture>();
let _filter_future_type = std::any::type_name::<AsyncPickerFilterFuture>();
println!("✓ Async picker types available");
}
#[test]
#[cfg(feature = "async")]
fn test_async_picker_future_is_future() {
use screencapturekit::async_api::AsyncPickerFuture;
use std::future::Future;
fn assert_future<T: Future>() {}
assert_future::<AsyncPickerFuture>();
println!("✓ AsyncPickerFuture implements Future");
}
#[test]
#[cfg(feature = "async")]
fn test_async_picker_filter_future_is_future() {
use screencapturekit::async_api::AsyncPickerFilterFuture;
use std::future::Future;
fn assert_future<T: Future>() {}
assert_future::<AsyncPickerFilterFuture>();
println!("✓ AsyncPickerFilterFuture implements Future");
}
#[test]
fn test_picker_is_active_get_set_roundtrip() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
let original = SCContentSharingPicker::is_active();
SCContentSharingPicker::set_active(true);
assert!(
SCContentSharingPicker::is_active(),
"is_active() returned false immediately after set_active(true)"
);
SCContentSharingPicker::set_active(false);
assert!(
!SCContentSharingPicker::is_active(),
"is_active() returned true immediately after set_active(false)"
);
SCContentSharingPicker::set_active(original);
}
static PICKER_STATE: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn exclusive_registry() -> std::sync::MutexGuard<'static, ()> {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let guard = PICKER_STATE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
SCContentSharingPicker::remove_all_observers();
guard
}
#[test]
fn add_observer_returns_a_live_subscription() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
let subscription = SCContentSharingPicker::add_observer(|_event| {});
if !subscription.is_active() {
assert_eq!(subscription.token(), 0);
return;
}
assert!(
subscription.is_active(),
"add_observer returned an inactive subscription"
);
assert_ne!(
subscription.token(),
0,
"live subscription must have a token"
);
assert!(
subscription.unsubscribe(),
"unsubscribe reported no removal"
);
}
#[test]
fn dropping_a_subscription_removes_the_observer() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
let token = {
let subscription = SCContentSharingPicker::add_observer(|_event| {});
subscription.token()
};
if token == 0 {
assert_eq!(SCContentSharingPicker::remove_all_observers(), 0);
return;
}
assert_ne!(token, 0);
assert_eq!(
SCContentSharingPicker::remove_all_observers(),
0,
"observer survived the drop of its subscription"
);
}
#[test]
fn unsubscribing_twice_is_not_reported_twice() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
let subscription = SCContentSharingPicker::add_observer(|_event| {});
let was_active = subscription.is_active();
assert_eq!(subscription.unsubscribe(), was_active);
assert_eq!(SCContentSharingPicker::remove_all_observers(), 0);
}
#[test]
fn remove_all_observers_sweeps_detached_registrations() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
let first = SCContentSharingPicker::add_observer(|_event| {});
let second = SCContentSharingPicker::add_observer(|_event| {});
let expected = usize::from(first.is_active()) + usize::from(second.is_active());
first.detach();
second.detach();
assert_eq!(
SCContentSharingPicker::remove_all_observers(),
expected,
"detached observers were not swept"
);
}
#[test]
fn observer_registration_does_not_leak_across_many_cycles() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
let _guard = exclusive_registry();
let counter = Arc::new(AtomicUsize::new(0));
for _ in 0..16 {
let counter = Arc::clone(&counter);
let subscription = SCContentSharingPicker::add_observer(move |_event| {
counter.fetch_add(1, Ordering::Relaxed);
});
drop(subscription);
}
assert_eq!(SCContentSharingPicker::remove_all_observers(), 0);
assert_eq!(
Arc::strong_count(&counter),
1,
"observer contexts retained captured state after teardown"
);
assert_eq!(
counter.load(Ordering::Relaxed),
0,
"an observer fired without any user interaction"
);
}
#[test]
fn set_default_configuration_is_reflected_by_default_configuration() {
use screencapturekit::content_sharing_picker::{
SCContentSharingPicker, SCContentSharingPickerConfiguration,
};
let mut config = SCContentSharingPickerConfiguration::new();
config.set_allows_changing_selected_content(true);
config.set_excluded_bundle_ids(&["com.example.picker-test"]);
SCContentSharingPicker::set_default_configuration(&config);
let read_back = SCContentSharingPicker::default_configuration();
assert!(!read_back.as_ptr().is_null());
}
#[test]
fn excluded_bundle_ids_round_trip() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
let mut config = SCContentSharingPickerConfiguration::new();
let ids = ["com.apple.dock", "com.apple.finder"];
config.set_excluded_bundle_ids(&ids);
assert_eq!(config.excluded_bundle_ids_count(), ids.len());
assert_eq!(config.excluded_bundle_ids(), ids);
}
#[test]
fn excluded_window_ids_round_trip() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
let mut config = SCContentSharingPickerConfiguration::new();
config.set_excluded_window_ids(&[7, 42, 1009]);
assert_eq!(config.excluded_window_ids(), vec![7, 42, 1009]);
}
#[test]
fn deactivate_clears_the_active_flag() {
use screencapturekit::content_sharing_picker::SCContentSharingPicker;
let _guard = exclusive_registry();
SCContentSharingPicker::set_active(true);
SCContentSharingPicker::deactivate();
for _ in 0..20 {
if !SCContentSharingPicker::is_active() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
SCContentSharingPicker::set_active(false);
assert!(!SCContentSharingPicker::is_active());
}
#[test]
fn clone_does_not_alias_the_original() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
let mut original = SCContentSharingPickerConfiguration::new();
original.set_excluded_bundle_ids(&["com.example.original"]);
original.set_excluded_window_ids(&[1]);
original.set_allows_changing_selected_content(false);
let mut copy = original.clone();
copy.set_excluded_bundle_ids(&["com.example.copy"]);
copy.set_excluded_window_ids(&[2, 3]);
copy.set_allows_changing_selected_content(true);
assert_eq!(original.excluded_bundle_ids(), ["com.example.original"]);
assert_eq!(original.excluded_window_ids(), vec![1]);
assert!(!original.allows_changing_selected_content());
assert_eq!(copy.excluded_bundle_ids(), ["com.example.copy"]);
assert_eq!(copy.excluded_window_ids(), vec![2, 3]);
assert!(copy.allows_changing_selected_content());
}
#[test]
fn clone_carries_the_source_values_forward() {
use screencapturekit::content_sharing_picker::{
SCContentSharingPickerConfiguration, SCContentSharingPickerMode,
};
let mut original = SCContentSharingPickerConfiguration::new();
let modes = [
SCContentSharingPickerMode::SingleDisplay,
SCContentSharingPickerMode::MultipleWindows,
];
original.set_allowed_picker_modes(&modes);
original.set_excluded_bundle_ids(&["com.apple.dock"]);
original.set_excluded_window_ids(&[11, 22]);
original.set_allows_changing_selected_content(true);
let copy = original.clone();
assert_eq!(copy.allowed_picker_modes(), original.allowed_picker_modes());
assert_eq!(copy.excluded_bundle_ids(), original.excluded_bundle_ids());
assert_eq!(copy.excluded_window_ids(), original.excluded_window_ids());
assert_eq!(
copy.allows_changing_selected_content(),
original.allows_changing_selected_content()
);
}
#[test]
fn clone_survives_the_original_being_dropped() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
let copy = {
let mut original = SCContentSharingPickerConfiguration::new();
original.set_excluded_bundle_ids(&["com.example.scoped"]);
original.clone()
};
assert_eq!(copy.excluded_bundle_ids(), ["com.example.scoped"]);
}
#[test]
fn clones_are_independently_mutable_across_threads() {
use screencapturekit::content_sharing_picker::SCContentSharingPickerConfiguration;
let mut template = SCContentSharingPickerConfiguration::new();
template.set_excluded_window_ids(&[0]);
let handles: Vec<_> = (1..=4u32)
.map(|i| {
let mut config = template.clone();
std::thread::spawn(move || {
for _ in 0..64 {
config.set_excluded_window_ids(&[i, i * 10]);
assert_eq!(config.excluded_window_ids(), vec![i, i * 10]);
}
})
})
.collect();
for handle in handles {
handle.join().expect("worker thread panicked");
}
assert_eq!(template.excluded_window_ids(), vec![0]);
}