diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index e8812df5a..092676322 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -59,8 +59,15 @@ dioxus-core-macro = { path = "../core-macro" } dioxus-hooks = { path = "../hooks" } 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 = "headless_tests" -path = "headless_tests/main.rs" -harness = false \ No newline at end of file +name = "check_rendering" +path = "headless_tests/rendering.rs" +harness = false diff --git a/packages/desktop/headless_tests/events.rs b/packages/desktop/headless_tests/events.rs index 60962c868..ec77850bd 100644 --- a/packages/desktop/headless_tests/events.rs +++ b/packages/desktop/headless_tests/events.rs @@ -1,9 +1,25 @@ -use crate::check_app_exits; + use dioxus::prelude::*; use dioxus_desktop::DesktopContext; use dioxus::html::geometry::euclid::Vector3D; -pub fn test_events() { +pub(crate) fn check_app_exits(app: Component) { + // 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(app); + + should_panic.store(false, std::sync::atomic::Ordering::SeqCst); +} + +pub fn main() { check_app_exits(app); } @@ -194,6 +210,7 @@ fn app(cx: Scope) -> Element { if **recieved_events == 12 { + println!("all events recieved"); desktop_context.close(); } diff --git a/packages/desktop/headless_tests/main.rs b/packages/desktop/headless_tests/main.rs deleted file mode 100644 index a9d9ec61c..000000000 --- a/packages/desktop/headless_tests/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Check that all events are being forwarded to the mock. -//! This example roughly shows how events are serialized into Rust from JavaScript. -//! -//! There is some conversion happening when input types are checkbox/radio/select/textarea etc. - -use dioxus::prelude::*; - -mod events; - -fn main() { - events::test_events(); -} - -pub(crate) fn check_app_exits(app: Component) { - // 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(app); - - should_panic.store(false, std::sync::atomic::Ordering::SeqCst); -} diff --git a/packages/desktop/headless_tests/rendering.rs b/packages/desktop/headless_tests/rendering.rs new file mode 100644 index 000000000..9a2c8bf57 --- /dev/null +++ b/packages/desktop/headless_tests/rendering.rs @@ -0,0 +1,89 @@ +use dioxus::prelude::*; +use dioxus_desktop::DesktopContext; + +pub(crate) fn check_app_exits(app: Component) { + // 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(app); + + 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 { + let value: &UseRef> = 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#"

text

hello world

"#; + +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: "

hello world

", + } + }; + + 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 + } + } + } +}