use proptest::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
fn valid_url_strategy() -> impl Strategy<Value = String> {
let scheme = "[a-z]{1,10}";
let path = "[a-zA-Z0-9._~:/?#@!$&'()*+,;=%\\-]{1,100}";
(scheme, path).prop_map(|(s, p)| format!("{s}://{p}"))
}
fn dispatch_open_urls(urls: Vec<String>, callback: &mut Option<Box<dyn FnMut(Vec<String>)>>) {
if let Some(cb) = callback.as_mut() {
cb(urls);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn url_callback_preserves_url_string(url in valid_url_strategy()) {
let received = Rc::new(RefCell::new(Vec::<String>::new()));
let received_clone = Rc::clone(&received);
let mut callback: Option<Box<dyn FnMut(Vec<String>)>> = Some(Box::new(move |urls| {
received_clone.borrow_mut().extend(urls);
}));
let dispatched = vec![url.clone()];
dispatch_open_urls(dispatched, &mut callback);
let received_urls = received.borrow();
prop_assert_eq!(received_urls.len(), 1, "callback should receive exactly one URL");
prop_assert_eq!(&received_urls[0], &url, "callback must receive the exact URL that was dispatched");
}
#[test]
fn url_callback_preserves_multiple_urls(urls in prop::collection::vec(valid_url_strategy(), 1..10)) {
let received = Rc::new(RefCell::new(Vec::<String>::new()));
let received_clone = Rc::clone(&received);
let mut callback: Option<Box<dyn FnMut(Vec<String>)>> = Some(Box::new(move |u| {
received_clone.borrow_mut().extend(u);
}));
let dispatched = urls.clone();
dispatch_open_urls(dispatched, &mut callback);
let received_urls = received.borrow();
prop_assert_eq!(received_urls.len(), urls.len(), "callback should receive all dispatched URLs");
for (i, (got, expected)) in received_urls.iter().zip(urls.iter()).enumerate() {
prop_assert_eq!(got, expected, "URL at index {} must match", i);
}
}
}
fn hotkey_id_strategy() -> impl Strategy<Value = u32> {
any::<u32>()
}
fn dispatch_global_hotkey_down(id: u32, callback: &mut Option<Box<dyn FnMut(u32)>>) {
if let Some(cb) = callback.as_mut() {
cb(id);
}
}
fn dispatch_global_hotkey_up(id: u32, callback: &mut Option<Box<dyn FnMut(u32)>>) {
if let Some(cb) = callback.as_mut() {
cb(id);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn global_hotkey_down_dispatches_correct_id(id in hotkey_id_strategy()) {
let received = Rc::new(RefCell::new(None::<u32>));
let received_clone = Rc::clone(&received);
let mut callback: Option<Box<dyn FnMut(u32)>> = Some(Box::new(move |hotkey_id| {
*received_clone.borrow_mut() = Some(hotkey_id);
}));
dispatch_global_hotkey_down(id, &mut callback);
let got = received.borrow();
prop_assert!(got.is_some(), "on_global_hotkey callback must be invoked");
prop_assert_eq!(got.unwrap(), id, "on_global_hotkey must receive the exact hotkey ID that was dispatched");
}
#[test]
fn global_hotkey_up_dispatches_correct_id(id in hotkey_id_strategy()) {
let received = Rc::new(RefCell::new(None::<u32>));
let received_clone = Rc::clone(&received);
let mut callback: Option<Box<dyn FnMut(u32)>> = Some(Box::new(move |hotkey_id| {
*received_clone.borrow_mut() = Some(hotkey_id);
}));
dispatch_global_hotkey_up(id, &mut callback);
let got = received.borrow();
prop_assert!(got.is_some(), "on_global_hotkey_up callback must be invoked");
prop_assert_eq!(got.unwrap(), id, "on_global_hotkey_up must receive the exact hotkey ID that was dispatched");
}
#[test]
fn global_hotkey_down_then_up_preserves_id(id in hotkey_id_strategy()) {
let down_received = Rc::new(RefCell::new(None::<u32>));
let up_received = Rc::new(RefCell::new(None::<u32>));
let down_clone = Rc::clone(&down_received);
let up_clone = Rc::clone(&up_received);
let mut down_callback: Option<Box<dyn FnMut(u32)>> = Some(Box::new(move |hotkey_id| {
*down_clone.borrow_mut() = Some(hotkey_id);
}));
let mut up_callback: Option<Box<dyn FnMut(u32)>> = Some(Box::new(move |hotkey_id| {
*up_clone.borrow_mut() = Some(hotkey_id);
}));
dispatch_global_hotkey_down(id, &mut down_callback);
dispatch_global_hotkey_up(id, &mut up_callback);
let down_id = down_received.borrow();
let up_id = up_received.borrow();
prop_assert!(down_id.is_some(), "on_global_hotkey callback must be invoked on key-down");
prop_assert!(up_id.is_some(), "on_global_hotkey_up callback must be invoked on key-up");
prop_assert_eq!(down_id.unwrap(), id, "on_global_hotkey must receive the dispatched ID");
prop_assert_eq!(up_id.unwrap(), id, "on_global_hotkey_up must receive the dispatched ID");
prop_assert_eq!(down_id.unwrap(), up_id.unwrap(), "both callbacks must receive the same hotkey ID");
}
}
use crate::MediaKeyEvent;
fn media_key_event_strategy() -> impl Strategy<Value = MediaKeyEvent> {
(0u8..6).prop_map(|i| match i {
0 => MediaKeyEvent::Play,
1 => MediaKeyEvent::Pause,
2 => MediaKeyEvent::PlayPause,
3 => MediaKeyEvent::Stop,
4 => MediaKeyEvent::NextTrack,
_ => MediaKeyEvent::PreviousTrack,
})
}
fn dispatch_media_key_event(
event: MediaKeyEvent,
callback: &mut Option<Box<dyn FnMut(MediaKeyEvent)>>,
) {
if let Some(cb) = callback.as_mut() {
cb(event);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn media_key_event_dispatch_preserves_variant(event in media_key_event_strategy()) {
let received = Rc::new(RefCell::new(None::<MediaKeyEvent>));
let received_clone = Rc::clone(&received);
let mut callback: Option<Box<dyn FnMut(MediaKeyEvent)>> = Some(Box::new(move |e| {
*received_clone.borrow_mut() = Some(e);
}));
dispatch_media_key_event(event, &mut callback);
let got = received.borrow();
prop_assert!(got.is_some(), "on_media_key_event callback must be invoked");
prop_assert_eq!(got.unwrap(), event, "on_media_key_event must receive the exact MediaKeyEvent variant that was dispatched");
}
}