argui-platform 0.2.0

Native platform integrations for Argui applications
Documentation
#![cfg(all(feature = "file-picker", not(target_arch = "wasm32")))]
use argui_platform::file_picker::*;
use std::sync::{Arc, Mutex};

struct Recorder(Arc<Mutex<Option<FileDialog>>>);
impl FileDialogBackend for Recorder {
    fn open(&self, dialog: FileDialog) -> FileDialogFuture {
        *self.0.lock().unwrap() = Some(dialog);
        Box::pin(async { Ok(None) })
    }
}

#[test]
fn options_reach_the_provider_and_empty_results_are_not_fake_selections() {
    for mode in [
        FilePickerMode::File,
        FilePickerMode::Files,
        FilePickerMode::Folder,
        FilePickerMode::Folders,
        FilePickerMode::Save,
    ] {
        assert!(mode.supported());
        let dialog = FileDialog::new(mode)
            .title("Select a document")
            .directory(std::env::temp_dir())
            .file_name("notes.md")
            .filter(FileFilter::new("Documents", ["md", "tar.gz"]))
            .filter(FileFilter::new("Any file", ["*"]));
        let recorded = Arc::new(Mutex::new(None));
        assert!(
            pollster::block_on(dialog.open_with(&Recorder(recorded.clone())))
                .unwrap()
                .is_none()
        );
        let sent = recorded.lock().unwrap().take().unwrap();
        assert_eq!(sent.mode, mode);
        assert_eq!(sent.title, "Select a document");
        assert_eq!(sent.file_name.as_deref(), Some("notes.md"));
        assert_eq!(sent.directory, Some(std::env::temp_dir()));
        assert_eq!(sent.filters[0].extensions, ["md", "tar.gz"]);
        assert!(format!("{sent:?}").contains("Documents"));
    }
    let default = FileDialog::default();
    assert_eq!(default.mode, FilePickerMode::File);
    assert!(default.validate().is_ok());
}

#[test]
fn invalid_options_are_rejected_before_opening_any_native_window() {
    let mut invalid = vec![FileDialog::default().title("a\0b")];
    for name in ["", ".", "..", "folder/name", "folder\\name", "a\0b"] {
        invalid.push(FileDialog::default().file_name(name));
    }
    for filter in [
        FileFilter::new("", ["txt"]),
        FileFilter::new("a\0b", ["txt"]),
        FileFilter::new("Files", [] as [&str; 0]),
    ] {
        invalid.push(FileDialog::default().filter(filter));
    }
    for extension in [
        "", ".txt", "a b", "*.rs", "a?", "a/b", "a\\b", "a;b", "a,b", "a\0b",
    ] {
        invalid.push(FileDialog::default().filter(FileFilter::new("Files", [extension])));
    }
    invalid.push(FileDialog::default().directory("bad\0path"));
    let recorded = Arc::new(Mutex::new(None));
    for dialog in invalid {
        let error =
            pollster::block_on(dialog.clone().open_with(&Recorder(recorded.clone()))).unwrap_err();
        assert!(matches!(error, FileDialogError::InvalidOptions(_)));
        assert!(!error.to_string().is_empty());
        assert!(pollster::block_on(dialog.open()).is_err());
    }
    assert!(recorded.lock().unwrap().is_none());
    assert!(
        FileDialogError::UnsupportedMode(FilePickerMode::Folder)
            .to_string()
            .contains("Folder")
    );
    assert_eq!(
        FileDialogError::Unavailable("service missing".into()).to_string(),
        "service missing"
    );
}

#[test]
fn parent_owner_is_retained_by_the_request() {
    use winit::raw_window_handle::{
        DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
    };
    struct UnavailableParent;
    impl HasWindowHandle for UnavailableParent {
        fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
            Err(HandleError::Unavailable)
        }
    }
    impl HasDisplayHandle for UnavailableParent {
        fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
            Err(HandleError::Unavailable)
        }
    }
    let parent = Arc::new(UnavailableParent);
    let weak = Arc::downgrade(&parent);
    let dialog = FileDialog::default().parent(parent);
    assert!(weak.upgrade().is_some());
    assert!(matches!(
        pollster::block_on(dialog.open()),
        Err(FileDialogError::Unavailable(_))
    ));
    assert!(weak.upgrade().is_none());
}