Merge pull request #995 from Demonthos/mock-event-tests
Mock Desktop Tests
This commit is contained in:
4 files changed
+461
-3
No files matched your search
+2
-2
@@ -42,10 +42,10 @@ private = true
|
|||||||
[tasks.test]
|
[tasks.test]
|
||||||
dependencies = ["build"]
|
dependencies = ["build"]
|
||||||
command = "cargo"
|
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
|
private = true
|
||||||
|
|
||||||
[tasks.test-with-browser]
|
[tasks.test-with-browser]
|
||||||
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] }
|
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] }
|
||||||
private = true
|
private = true
|
||||||
workspace = true
|
workspace = true
|
||||||
@@ -57,4 +57,17 @@ hot-reload = ["dioxus-hot-reload"]
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dioxus-core-macro = { path = "../core-macro" }
|
dioxus-core-macro = { path = "../core-macro" }
|
||||||
dioxus-hooks = { path = "../hooks" }
|
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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user