Browse Source

wip: add tests and slightly refactor interpreter code

Jonathan Kelley 1 year ago
parent
commit
53cafefdef

+ 13 - 4
examples/form.rs

@@ -4,19 +4,24 @@
 //! in the "values" field.
 
 use dioxus::prelude::*;
+use std::collections::HashMap;
 
 fn main() {
-    launch_desktop(app);
+    launch(app);
 }
 
 fn app() -> Element {
+    let mut values = use_signal(|| HashMap::new());
     rsx! {
         div {
             h1 { "Form" }
             form {
-                onsubmit: move |ev| println!("Submitted {:?}", ev.values()),
-                oninput: move |ev| println!("Input {:?}", ev.values()),
-                input { r#type: "text", name: "username" }
+                oninput: move |ev| values.set(ev.values()),
+                input {
+                    r#type: "text",
+                    name: "username",
+                    oninput: move |ev| values.set(ev.values())
+                }
                 input { r#type: "text", name: "full-name" }
                 input { r#type: "password", name: "password" }
                 input { r#type: "radio", name: "color", value: "red" }
@@ -24,5 +29,9 @@ fn app() -> Element {
                 button { r#type: "submit", value: "Submit", "Submit the form" }
             }
         }
+        div {
+            h1 { "Oninput Values" }
+            "{values:#?}"
+        }
     }
 }

+ 328 - 248
packages/desktop/headless_tests/events.rs

@@ -1,73 +1,96 @@
+use std::{collections::HashMap, ops::Deref};
+
 use dioxus::html::geometry::euclid::Vector3D;
 use dioxus::prelude::*;
 use dioxus_core::prelude::consume_context;
 use dioxus_desktop::DesktopContext;
 
+#[path = "./utils.rs"]
+mod utils;
+
 pub fn main() {
-    check_app_exits(app);
+    utils::check_app_exits(app);
 }
 
-pub(crate) fn check_app_exits(app: fn() -> Element) {
-    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(60));
-        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
-            eprintln!("App did not exit in time");
-            std::process::exit(exitcode::SOFTWARE);
-        }
-    });
+static RECEIVED_EVENTS: GlobalSignal<usize> = Signal::global(|| 0);
 
-    LaunchBuilder::desktop()
-        .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(true)))
-        .launch(app);
+fn app() -> Element {
+    let desktop_context: DesktopContext = consume_context();
 
-    // Stop deadman's switch
-    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
-}
+    let received = RECEIVED_EVENTS();
+    let expected = utils::EXPECTED_EVENTS();
 
-fn mock_event(id: &'static str, value: &'static str) {
-    use_hook(move || {
-        spawn(async move {
-            tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
-
-            let js = format!(
-                r#"
-                //console.log("ran");
-                // Dispatch a synthetic event
-                let event = {};
-                let element = document.getElementById('{}');
-                console.log(element, event);
-                element.dispatchEvent(event);
-                "#,
-                value, id
-            );
-
-            eval(&js).await.unwrap();
-        });
-    })
+    if expected != 0 && received == expected {
+        println!("all events recieved");
+        desktop_context.close();
+    }
+
+    rsx! {
+        div {
+            test_mounted {}
+            test_button {}
+            test_mouse_move_div {}
+            test_mouse_click_div {}
+            test_mouse_dblclick_div {}
+            test_mouse_down_div {}
+            test_mouse_up_div {}
+            test_mouse_scroll_div {}
+            test_key_down_div {}
+            test_key_up_div {}
+            test_key_press_div {}
+            test_focus_in_div {}
+            test_focus_out_div {}
+            test_form_input {}
+        }
+    }
 }
 
-#[allow(deprecated)]
-fn app() -> Element {
-    let desktop_context: DesktopContext = consume_context();
-    let mut received_events = use_signal(|| 0);
+fn test_mounted() -> Element {
+    rsx! {
+        div {
+            width: "100px",
+            height: "100px",
+            onmounted: move |evt| async move {
+                let rect = evt.get_client_rect().await.unwrap();
+                println!("rect: {:?}", rect);
+                assert_eq!(rect.width(), 100.0);
+                assert_eq!(rect.height(), 100.0);
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
 
-    // button
-    mock_event(
+fn test_button() -> Element {
+    utils::mock_event(
         "button",
         r#"new MouseEvent("click", {
-        view: window,
-        bubbles: true,
-        cancelable: true,
-        button: 0,
+            view: window,
+            bubbles: true,
+            cancelable: true,
+            button: 0,
         })"#,
     );
-    // mouse_move_div
-    mock_event(
+
+    rsx! {
+        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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_move_div() -> Element {
+    utils::mock_event(
         "mouse_move_div",
         r#"new MouseEvent("mousemove", {
         view: window,
@@ -76,8 +99,27 @@ fn app() -> Element {
         buttons: 2,
         })"#,
     );
-    // mouse_click_div
-    mock_event(
+
+    rsx! {
+        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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_click_div() -> Element {
+    utils::mock_event(
         "mouse_click_div",
         r#"new MouseEvent("click", {
         view: window,
@@ -87,8 +129,31 @@ fn app() -> Element {
         button: 2,
         })"#,
     );
-    // mouse_dblclick_div
-    mock_event(
+
+    rsx! {
+        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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_dblclick_div() -> Element {
+    utils::mock_event(
         "mouse_dblclick_div",
         r#"new MouseEvent("dblclick", {
         view: window,
@@ -98,8 +163,34 @@ fn app() -> Element {
         button: 2,
         })"#,
     );
-    // mouse_down_div
-    mock_event(
+
+    rsx! {
+        div {
+            id: "mouse_dblclick_div",
+            ondoubleclick: 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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_down_div() -> Element {
+    utils::mock_event(
         "mouse_down_div",
         r#"new MouseEvent("mousedown", {
         view: window,
@@ -109,8 +200,31 @@ fn app() -> Element {
         button: 2,
         })"#,
     );
-    // mouse_up_div
-    mock_event(
+
+    rsx! {
+        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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_up_div() -> Element {
+    utils::mock_event(
         "mouse_up_div",
         r#"new MouseEvent("mouseup", {
         view: window,
@@ -120,8 +234,26 @@ fn app() -> Element {
         button: 0,
         })"#,
     );
-    // wheel_div
-    mock_event(
+
+    rsx! {
+        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),
+                );
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_mouse_scroll_div() -> Element {
+    utils::mock_event(
         "wheel_div",
         r#"new WheelEvent("wheel", {
         view: window,
@@ -132,8 +264,26 @@ fn app() -> Element {
         bubbles: true,
         })"#,
     );
-    // key_down_div
-    mock_event(
+
+    rsx! {
+        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));
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_key_down_div() -> Element {
+    utils::mock_event(
         "key_down_div",
         r#"new KeyboardEvent("keydown", {
         key: "a",
@@ -153,8 +303,24 @@ fn app() -> Element {
         bubbles: true,
         })"#,
     );
-    // key_up_div
-    mock_event(
+    rsx! {
+        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(), Location::Standard);
+                assert!(event.data.is_auto_repeating());
+                assert!(event.data.is_composing());
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+fn test_key_up_div() -> Element {
+    utils::mock_event(
         "key_up_div",
         r#"new KeyboardEvent("keyup", {
         key: "a",
@@ -174,8 +340,25 @@ fn app() -> Element {
         bubbles: true,
         })"#,
     );
-    // key_press_div
-    mock_event(
+
+    rsx! {
+        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(), Location::Standard);
+                assert!(!event.data.is_auto_repeating());
+                assert!(!event.data.is_composing());
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+fn test_key_press_div() -> Element {
+    utils::mock_event(
         "key_press_div",
         r#"new KeyboardEvent("keypress", {
         key: "a",
@@ -195,197 +378,94 @@ fn app() -> Element {
         bubbles: true,
         })"#,
     );
-    // focus_in_div
-    mock_event(
+    rsx! {
+        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(), Location::Standard);
+                assert!(!event.data.is_auto_repeating());
+                assert!(!event.data.is_composing());
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_focus_in_div() -> Element {
+    utils::mock_event(
         "focus_in_div",
         r#"new FocusEvent("focusin", {bubbles: true})"#,
     );
-    // focus_out_div
-    mock_event(
+
+    rsx! {
+        input {
+            id: "focus_in_div",
+            onfocusin: move |event| {
+                println!("{:?}", event.data);
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
+    }
+}
+
+fn test_focus_out_div() -> Element {
+    utils::mock_event(
         "focus_out_div",
         r#"new FocusEvent("focusout",{bubbles: true})"#,
     );
-
-    if received_events() == 13 {
-        println!("all events recieved");
-        desktop_context.close();
+    rsx! {
+        input {
+            id: "focus_out_div",
+            onfocusout: move |event| {
+                println!("{:?}", event.data);
+                RECEIVED_EVENTS.with_mut(|x| *x += 1);
+            }
+        }
     }
+}
+
+fn test_form_input() -> Element {
+    let mut values = use_signal(|| HashMap::new());
+
+    utils::mock_event_with_extra(
+        "form-username",
+        r#"new Event("input", {
+        bubbles: true,
+        cancelable: true,
+        composed: true,
+        })"#,
+        r#"element.value = "hello";"#,
+    );
+
+    let set_values = move |ev: FormEvent| {
+        values.set(ev.values());
+        eprintln!("values: {:?}", values);
+        values.with_mut(|x| {
+            assert_eq!(x.get("username").unwrap().deref(), &["hello"]);
+        });
+    };
 
     rsx! {
         div {
-            div {
-                width: "100px",
-                height: "100px",
-                onmounted: move |evt| async move {
-                    let rect = evt.get_client_rect().await.unwrap();
-                    println!("rect: {:?}", rect);
-                    assert_eq!(rect.width(), 100.0);
-                    assert_eq!(rect.height(), 100.0);
-                    received_events.with_mut(|x| *x += 1);
-                }
-            }
-            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),
-                    );
-                    received_events.with_mut(|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),
-                    );
-                    received_events.with_mut(|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),
-                    );
-                    received_events.with_mut(|x| *x += 1);
-                }
-            }
-            div {
-                id: "mouse_dblclick_div",
-                ondoubleclick: 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),
-                    );
-                    received_events.with_mut(|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),
-                    );
-                    received_events.with_mut(|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),
-                    );
-                    received_events.with_mut(|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));
-                    received_events.with_mut(|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(), Location::Standard);
-                    assert!(event.data.is_auto_repeating());
-                    assert!(event.data.is_composing());
-                    received_events.with_mut(|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(), Location::Standard);
-                    assert!(!event.data.is_auto_repeating());
-                    assert!(!event.data.is_composing());
-                    received_events.with_mut(|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(), Location::Standard);
-                    assert!(!event.data.is_auto_repeating());
-                    assert!(!event.data.is_composing());
-                    received_events.with_mut(|x| *x += 1);
-                }
-            }
-            input {
-                id: "focus_in_div",
-                onfocusin: move |event| {
-                    println!("{:?}", event.data);
-                    received_events.with_mut(|x| *x += 1);
-                }
-            }
-            input {
-                id: "focus_out_div",
-                onfocusout: move |event| {
-                    println!("{:?}", event.data);
-                    received_events.with_mut(|x| *x += 1);
+            h1 { "Form" }
+            form {
+                id: "form",
+                oninput: move |ev| values.set(ev.values()),
+                input {
+                    r#type: "text",
+                    name: "username",
+                    id: "form-username",
+                    oninput: set_values,
                 }
+                input { r#type: "text", name: "full-name" }
+                input { r#type: "password", name: "password" }
+                input { r#type: "radio", name: "color", value: "red" }
+                input { r#type: "radio", name: "color", value: "blue" }
+                button { r#type: "submit", value: "Submit", "Submit the form" }
             }
         }
     }

+ 4 - 21
packages/desktop/headless_tests/rendering.rs

@@ -2,28 +2,11 @@ use dioxus::prelude::*;
 use dioxus_core::Element;
 use dioxus_desktop::DesktopContext;
 
-fn main() {
-    check_app_exits(check_html_renders);
-}
+#[path = "./utils.rs"]
+mod utils;
 
-pub(crate) fn check_app_exits(app: fn() -> Element) {
-    use dioxus_desktop::Config;
-    use tao::window::WindowBuilder;
-    // 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(5));
-        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
-            std::process::exit(exitcode::SOFTWARE);
-        }
-    });
-
-    LaunchBuilder::desktop()
-        .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(true)))
-        .launch(app);
-
-    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
+fn main() {
+    utils::check_app_exits(check_html_renders);
 }
 
 fn use_inner_html(id: &'static str) -> Option<String> {

+ 55 - 0
packages/desktop/headless_tests/utils.rs

@@ -0,0 +1,55 @@
+use dioxus::prelude::*;
+use dioxus_core::Element;
+
+pub fn check_app_exits(app: fn() -> Element) {
+    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(60));
+        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
+            eprintln!("App did not exit in time");
+            std::process::exit(exitcode::SOFTWARE);
+        }
+    });
+
+    LaunchBuilder::desktop()
+        .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(true)))
+        .launch(app);
+
+    // Stop deadman's switch
+    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
+}
+
+pub static EXPECTED_EVENTS: GlobalSignal<usize> = Signal::global(|| 0);
+
+pub fn mock_event(id: &'static str, value: &'static str) {
+    mock_event_with_extra(id, value, "");
+}
+
+pub fn mock_event_with_extra(id: &'static str, value: &'static str, extra: &'static str) {
+    EXPECTED_EVENTS.with_mut(|x| *x += 1);
+
+    use_hook(move || {
+        spawn(async move {
+            tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
+
+            let js = format!(
+                r#"
+                //console.log("ran");
+                // Dispatch a synthetic event
+                let event = {};
+                let element = document.getElementById('{}');
+                console.log(element, event);
+                {}
+                element.dispatchEvent(event);
+                "#,
+                value, id, extra
+            );
+
+            eval(&js).await.unwrap();
+        });
+    })
+}

+ 151 - 122
packages/interpreter/src/interpreter.js

@@ -8,155 +8,182 @@ class InterpreterConfig {
 // method is not used by the web implementation
 async function handler(event, name, bubbles, config) {
   let target = event.target;
-  if (target != null) {
-    let preventDefaultRequests = null;
-    // Some events can be triggered on text nodes, which don't have attributes
-    if (target instanceof Element) {
-      preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
-    }
+  if (target == null) {
+    return;
+  }
 
-    if (event.type === "click") {
-      // todo call prevent default if it's the right type of event
-      if (config.intercept_link_redirects) {
-        let a_element = target.closest("a");
-        if (a_element != null) {
-          event.preventDefault();
-
-          let elementShouldPreventDefault =
-            preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
-          let aElementShouldPreventDefault = a_element.getAttribute(
-            `dioxus-prevent-default`
-          );
-          let linkShouldPreventDefault =
-            aElementShouldPreventDefault &&
-            aElementShouldPreventDefault.includes(`onclick`);
-
-          if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
-            const href = a_element.getAttribute("href");
-            if (href !== "" && href !== null && href !== undefined) {
-              window.ipc.postMessage(
-                window.interpreter.serializeIpcMessage("browser_open", { href })
-              );
-            }
-          }
-        }
-      }
+  const realId = find_real_id(target);
+  if (realId === null) {
+    return;
+  }
 
-      // also prevent buttons from submitting
-      if (target.tagName === "BUTTON" && event.type == "submit") {
-        event.preventDefault();
-      }
-    }
+  prevent_defaults(event, target, config);
 
-    const realId = find_real_id(target);
+  let contents = await serialize_event(event);
 
-    if (
-      preventDefaultRequests &&
-      preventDefaultRequests.includes(`on${event.type}`)
-    ) {
-      event.preventDefault();
-    }
+  // TODO: this should be liveview only
+  if (
+    target.tagName === "INPUT" &&
+    (event.type === "change" || event.type === "input")
+  ) {
+    const type = target.getAttribute("type");
 
-    if (event.type === "submit") {
-      event.preventDefault();
-    }
+    if (type === "file") {
+      async function read_files() {
+        const files = target.files;
+        const file_contents = {};
+
+        for (let i = 0; i < files.length; i++) {
+          const file = files[i];
 
-    let contents = await serialize_event(event);
-
-    // TODO: this should be liveview only
-    if (
-      target.tagName === "INPUT" &&
-      (event.type === "change" || event.type === "input")
-    ) {
-      const type = target.getAttribute("type");
-      if (type === "file") {
-        async function read_files() {
-          const files = target.files;
-          const file_contents = {};
-
-          for (let i = 0; i < files.length; i++) {
-            const file = files[i];
-
-            file_contents[file.name] = Array.from(
-              new Uint8Array(await file.arrayBuffer())
-            );
-          }
-          let file_engine = {
-            files: file_contents,
-          };
-          contents.files = file_engine;
-
-          if (realId === null) {
-            return;
-          }
-          const message = window.interpreter.serializeIpcMessage("user_event", {
-            name: name,
-            element: parseInt(realId),
-            data: contents,
-            bubbles,
-          });
-          window.ipc.postMessage(message);
+          file_contents[file.name] = Array.from(
+            new Uint8Array(await file.arrayBuffer())
+          );
+        }
+        let file_engine = {
+          files: file_contents,
+        };
+        contents.files = file_engine;
+
+        if (realId === null) {
+          return;
         }
-        read_files();
-        return;
+        const message = window.interpreter.serializeIpcMessage("user_event", {
+          name: name,
+          element: parseInt(realId),
+          data: contents,
+          bubbles,
+        });
+        window.ipc.postMessage(message);
       }
+      read_files();
+      return;
     }
+  }
 
-    if (
-      target.tagName === "FORM" &&
-      (event.type === "submit" || event.type === "input")
-    ) {
-      const formData = new FormData(target);
+  if (
+    target.tagName === "FORM" &&
+    (event.type === "submit" || event.type === "input")
+  ) {
+    const formData = new FormData(target);
 
-      for (let name of formData.keys()) {
-        const fieldType = target.elements[name].type;
+    for (let name of formData.keys()) {
+      const fieldType = target.elements[name].type;
 
-        switch (fieldType) {
-          case "select-multiple":
-            contents.values[name] = formData.getAll(name);
-            break;
+      switch (fieldType) {
+        case "select-multiple":
+          contents.values[name] = formData.getAll(name);
+          break;
 
-          // add cases for fieldTypes that can hold multiple values here
-          default:
-            contents.values[name] = formData.get(name);
-            break;
-        }
+        // add cases for fieldTypes that can hold multiple values here
+        default:
+          contents.values[name] = formData.get(name);
+          break;
       }
     }
+  }
 
-    if (
-      target.tagName === "SELECT" &&
-      event.type === "input"
-    ) {
-      const selectData = target.options;
-      contents.values["options"] = [];
-      for (let i = 0; i < selectData.length; i++) {
-        let option = selectData[i];
-        if (option.selected) {
-          contents.values["options"].push(option.value.toString());
-        }
+  if (
+    target.tagName === "SELECT" &&
+    event.type === "input"
+  ) {
+    const selectData = target.options;
+    contents.values["options"] = [];
+    for (let i = 0; i < selectData.length; i++) {
+      let option = selectData[i];
+      if (option.selected) {
+        contents.values["options"].push(option.value.toString());
       }
     }
+  }
 
-    if (realId === null) {
-      return;
+  window.ipc.postMessage(
+    window.interpreter.serializeIpcMessage("user_event", {
+      name: name,
+      element: parseInt(realId),
+      data: contents,
+      bubbles,
+    })
+  );
+}
+
+// Do our best to prevent the default action of the event
+// This should:
+// - prevent form submissions from navigating
+// - prevent anchor tags from navigating
+// - prevent buttons from submitting forms
+function prevent_defaults(event, target, config) {
+  let preventDefaultRequests = null;
+
+  // Some events can be triggered on text nodes, which don't have attributes
+  if (target instanceof Element) {
+    preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
+  }
+
+  if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
+    event.preventDefault();
+  }
+
+  if (event.type === "submit") {
+    event.preventDefault();
+  }
+
+  // Attempt to intercept if the event is a click
+  intercept_form_submit(event, target, config, preventDefaultRequests);
+}
+
+function intercept_form_submit(event, target, config, preventDefaultRequests) {
+  if (event.type !== "click") {
+    return;
+  }
+
+  // todo call prevent default if it's the right type of event
+  if (!config.intercept_link_redirects) {
+    return;
+  }
+
+  // prevent buttons in forms from submitting the form
+  if (target.tagName === "BUTTON" && event.type == "submit") {
+    event.preventDefault();
+  }
+
+  // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
+  let a_element = target.closest("a");
+
+  if (a_element == null) {
+    return;
+  }
+
+  event.preventDefault();
+
+  let elementShouldPreventDefault =
+    preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
+
+  let aElementShouldPreventDefault = a_element.getAttribute(
+    `dioxus-prevent-default`
+  );
+
+  let linkShouldPreventDefault =
+    aElementShouldPreventDefault &&
+    aElementShouldPreventDefault.includes(`onclick`);
+
+  if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
+    const href = a_element.getAttribute("href");
+    if (href !== "" && href !== null && href !== undefined) {
+      window.ipc.postMessage(
+        window.interpreter.serializeIpcMessage("browser_open", { href })
+      );
     }
-    window.ipc.postMessage(
-      window.interpreter.serializeIpcMessage("user_event", {
-        name: name,
-        element: parseInt(realId),
-        data: contents,
-        bubbles,
-      })
-    );
   }
 }
 
 function find_real_id(target) {
   let realId = null;
+
   if (target instanceof Element) {
     realId = target.getAttribute(`data-dioxus-id`);
   }
+
   // walk the tree to find the real element
   while (realId == null) {
     // we've reached the root we don't want to send an event
@@ -169,6 +196,7 @@ function find_real_id(target) {
       realId = target.getAttribute(`data-dioxus-id`);
     }
   }
+
   return realId;
 }
 
@@ -421,7 +449,8 @@ async function serialize_event(event) {
     case "drop": {
       let files = null;
       if (event.dataTransfer && event.dataTransfer.files) {
-        files = await serializeFileList(event.dataTransfer.files);
+        files = ["a", "b", "c"];
+        // files = await serializeFileList(event.dataTransfer.files);
       }
 
       return { mouse: get_mouse_data(event), files };