Sfoglia il codice sorgente

Merge pull request #995 from Demonthos/mock-event-tests

Mock Desktop Tests
Jon Kelley 2 anni fa
parent
commit
7a620daad7

+ 2 - 2
Makefile.toml

@@ -42,10 +42,10 @@ private = true
 [tasks.test]
 dependencies = ["build"]
 command = "cargo"
-args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router"]
+args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"]
 private = true
 
 [tasks.test-with-browser]
-env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] }
+env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] }
 private = true
 workspace = true

+ 14 - 1
packages/desktop/Cargo.toml

@@ -57,4 +57,17 @@ hot-reload = ["dioxus-hot-reload"]
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }
 dioxus-hooks = { path = "../hooks" }
-# image = "0.24.0" # enable this when generating a new desktop image
+dioxus = { path = "../dioxus" }
+exitcode = "1.1.2"
+scraper = "0.16.0"
+
+# These tests need to be run on the main thread, so they cannot use rust's test harness.
+[[test]]
+name = "check_events"
+path = "headless_tests/events.rs"
+harness = false
+
+[[test]]
+name = "check_rendering"
+path = "headless_tests/rendering.rs"
+harness = false

+ 351 - 0
packages/desktop/headless_tests/events.rs

@@ -0,0 +1,351 @@
+use dioxus::html::geometry::euclid::Vector3D;
+use dioxus::prelude::*;
+use dioxus_desktop::DesktopContext;
+
+pub(crate) fn check_app_exits(app: Component) {
+    use dioxus_desktop::tao::window::WindowBuilder;
+    use dioxus_desktop::Config;
+    // This is a deadman's switch to ensure that the app exits
+    let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
+    let should_panic_clone = should_panic.clone();
+    std::thread::spawn(move || {
+        std::thread::sleep(std::time::Duration::from_secs(100));
+        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
+            std::process::exit(exitcode::SOFTWARE);
+        }
+    });
+
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new().with_window(WindowBuilder::new().with_visible(false)),
+    );
+
+    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
+}
+
+pub fn main() {
+    check_app_exits(app);
+}
+
+fn mock_event(cx: &ScopeState, id: &'static str, value: &'static str) {
+    use_effect(cx, (), |_| {
+        let desktop_context: DesktopContext = cx.consume_context().unwrap();
+        async move {
+            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+            desktop_context.eval(&format!(
+                r#"let element = document.getElementById('{}');
+                // Dispatch a synthetic event
+                const event = {};
+                console.log(element, event);
+                element.dispatchEvent(event);
+                "#,
+                id, value
+            ));
+        }
+    });
+}
+
+#[allow(deprecated)]
+fn app(cx: Scope) -> Element {
+    let desktop_context: DesktopContext = cx.consume_context().unwrap();
+    let recieved_events = use_state(cx, || 0);
+
+    // button
+    mock_event(
+        cx,
+        "button",
+        r#"new MouseEvent("click", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    button: 0,
+  })"#,
+    );
+    // mouse_move_div
+    mock_event(
+        cx,
+        "mouse_move_div",
+        r#"new MouseEvent("mousemove", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    buttons: 2,
+    })"#,
+    );
+    // mouse_click_div
+    mock_event(
+        cx,
+        "mouse_click_div",
+        r#"new MouseEvent("click", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    buttons: 2,
+    button: 2,
+    })"#,
+    );
+    // mouse_dblclick_div
+    mock_event(
+        cx,
+        "mouse_dblclick_div",
+        r#"new MouseEvent("dblclick", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    buttons: 1|2,
+    button: 2,
+    })"#,
+    );
+    // mouse_down_div
+    mock_event(
+        cx,
+        "mouse_down_div",
+        r#"new MouseEvent("mousedown", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    buttons: 2,
+    button: 2,
+    })"#,
+    );
+    // mouse_up_div
+    mock_event(
+        cx,
+        "mouse_up_div",
+        r#"new MouseEvent("mouseup", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+    buttons: 0,
+    button: 0,
+    })"#,
+    );
+    // wheel_div
+    mock_event(
+        cx,
+        "wheel_div",
+        r#"new WheelEvent("wheel", {
+    view: window,
+    deltaX: 1.0,
+    deltaY: 2.0,
+    deltaZ: 3.0,
+    deltaMode: 0x00,
+    bubbles: true,
+    })"#,
+    );
+    // key_down_div
+    mock_event(
+        cx,
+        "key_down_div",
+        r#"new KeyboardEvent("keydown", {
+    key: "a",
+    code: "KeyA",
+    location: 0,
+    repeat: true,
+    keyCode: 65,
+    charCode: 97,
+    char: "a",
+    charCode: 0,
+    altKey: false,
+    ctrlKey: false,
+    metaKey: false,
+    shiftKey: false,
+    isComposing: false,
+    which: 65,
+    bubbles: true,
+    })"#,
+    );
+    // key_up_div
+    mock_event(
+        cx,
+        "key_up_div",
+        r#"new KeyboardEvent("keyup", {
+    key: "a",
+    code: "KeyA",
+    location: 0,
+    repeat: false,
+    keyCode: 65,
+    charCode: 97,
+    char: "a",
+    charCode: 0,
+    altKey: false,
+    ctrlKey: false,
+    metaKey: false,
+    shiftKey: false,
+    isComposing: false,
+    which: 65,
+    bubbles: true,
+    })"#,
+    );
+    // key_press_div
+    mock_event(
+        cx,
+        "key_press_div",
+        r#"new KeyboardEvent("keypress", {
+    key: "a",
+    code: "KeyA",
+    location: 0,
+    repeat: false,
+    keyCode: 65,
+    charCode: 97,
+    char: "a",
+    charCode: 0,
+    altKey: false,
+    ctrlKey: false,
+    metaKey: false,
+    shiftKey: false,
+    isComposing: false,
+    which: 65,
+    bubbles: true,
+    })"#,
+    );
+    // focus_in_div
+    mock_event(
+        cx,
+        "focus_in_div",
+        r#"new FocusEvent("focusin", {bubbles: true})"#,
+    );
+    // focus_out_div
+    mock_event(
+        cx,
+        "focus_out_div",
+        r#"new FocusEvent("focusout",{bubbles: true})"#,
+    );
+
+    if **recieved_events == 12 {
+        println!("all events recieved");
+        desktop_context.close();
+    }
+
+    cx.render(rsx! {
+        div {
+            button {
+                id: "button",
+                onclick: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().is_empty());
+                    assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
+                    recieved_events.modify(|x| *x + 1)
+                },
+            }
+            div {
+                id: "mouse_move_div",
+                onmousemove: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
+                    recieved_events.modify(|x| *x + 1)
+                },
+            }
+            div {
+                id: "mouse_click_div",
+                onclick: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
+                    assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
+                    recieved_events.modify(|x| *x + 1)
+                },
+            }
+            div{
+                id: "mouse_dblclick_div",
+                ondblclick: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary));
+                    assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
+                    assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            div{
+                id: "mouse_down_div",
+                onmousedown: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
+                    assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            div{
+                id: "mouse_up_div",
+                onmouseup: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert!(event.data.held_buttons().is_empty());
+                    assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            div{
+                id: "wheel_div",
+                width: "100px",
+                height: "100px",
+                background_color: "red",
+                onwheel: move |event| {
+                    println!("{:?}", event.data);
+                    let dioxus_html::geometry::WheelDelta::Pixels(delta)= event.data.delta()else{
+                        panic!("Expected delta to be in pixels")
+                    };
+                    assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            input{
+                id: "key_down_div",
+                onkeydown: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert_eq!(event.data.key().to_string(), "a");
+                    assert_eq!(event.data.code().to_string(), "KeyA");
+                    assert_eq!(event.data.location, 0);
+                    assert!(event.data.is_auto_repeating());
+
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            input{
+                id: "key_up_div",
+                onkeyup: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert_eq!(event.data.key().to_string(), "a");
+                    assert_eq!(event.data.code().to_string(), "KeyA");
+                    assert_eq!(event.data.location, 0);
+                    assert!(!event.data.is_auto_repeating());
+
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            input{
+                id: "key_press_div",
+                onkeypress: move |event| {
+                    println!("{:?}", event.data);
+                    assert!(event.data.modifiers().is_empty());
+                    assert_eq!(event.data.key().to_string(), "a");
+                    assert_eq!(event.data.code().to_string(), "KeyA");
+                    assert_eq!(event.data.location, 0);
+                    assert!(!event.data.is_auto_repeating());
+
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            input{
+                id: "focus_in_div",
+                onfocusin: move |event| {
+                    println!("{:?}", event.data);
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+            input{
+                id: "focus_out_div",
+                onfocusout: move |event| {
+                    println!("{:?}", event.data);
+                    recieved_events.modify(|x| *x + 1)
+                }
+            }
+        }
+    })
+}

+ 94 - 0
packages/desktop/headless_tests/rendering.rs

@@ -0,0 +1,94 @@
+use dioxus::prelude::*;
+use dioxus_desktop::DesktopContext;
+
+pub(crate) fn check_app_exits(app: Component) {
+    use dioxus_desktop::tao::window::WindowBuilder;
+    use dioxus_desktop::Config;
+    // This is a deadman's switch to ensure that the app exits
+    let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
+    let should_panic_clone = should_panic.clone();
+    std::thread::spawn(move || {
+        std::thread::sleep(std::time::Duration::from_secs(100));
+        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
+            std::process::exit(exitcode::SOFTWARE);
+        }
+    });
+
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new().with_window(WindowBuilder::new().with_visible(false)),
+    );
+
+    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
+}
+
+fn main() {
+    check_app_exits(check_html_renders);
+}
+
+fn use_inner_html(cx: &ScopeState, id: &'static str) -> Option<String> {
+    let value: &UseRef<Option<String>> = use_ref(cx, || None);
+    use_effect(cx, (), |_| {
+        to_owned![value];
+        let desktop_context: DesktopContext = cx.consume_context().unwrap();
+        async move {
+            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+            let html = desktop_context
+                .eval(&format!(
+                    r#"let element = document.getElementById('{}');
+                return element.innerHTML;"#,
+                    id
+                ))
+                .await;
+            if let Ok(serde_json::Value::String(html)) = html {
+                println!("html: {}", html);
+                value.set(Some(html));
+            }
+        }
+    });
+    value.read().clone()
+}
+
+const EXPECTED_HTML: &str = r#"<div id="5" style="width: 100px; height: 100px; color: rgb(0, 0, 0);"><input type="checkbox"><h1>text</h1><div><p>hello world</p></div></div>"#;
+
+fn check_html_renders(cx: Scope) -> Element {
+    let inner_html = use_inner_html(cx, "main_div");
+    let desktop_context: DesktopContext = cx.consume_context().unwrap();
+
+    if let Some(raw_html) = inner_html.as_deref() {
+        let fragment = scraper::Html::parse_fragment(raw_html);
+        println!("fragment: {:?}", fragment.html());
+        let expected = scraper::Html::parse_fragment(EXPECTED_HTML);
+        println!("fragment: {:?}", expected.html());
+        if fragment == expected {
+            println!("html matches");
+            desktop_context.close();
+        }
+    }
+
+    let dyn_value = 0;
+    let dyn_element = rsx! {
+        div {
+            dangerous_inner_html: "<p>hello world</p>",
+        }
+    };
+
+    render! {
+        div {
+            id: "main_div",
+            div {
+                width: "100px",
+                height: "100px",
+                color: "rgb({dyn_value}, {dyn_value}, {dyn_value})",
+                id: 5,
+                input {
+                    "type": "checkbox",
+                },
+                h1 {
+                    "text"
+                }
+                dyn_element
+            }
+        }
+    }
+}