use super::{expect_call, expect_call_with_args, InteractionHarness};
use anyhow::Result;
use iced_core::{Point, Size};
use netidx::publisher::Value;
const IMPORTS: &str = "\
use gui;\n\
use gui::text;\n\
use gui::button;\n\
use gui::checkbox;\n\
use gui::toggler;\n\
use gui::text_input;\n\
use gui::slider;\n\
use gui::radio;\n\
use gui::pick_list;\n\
use gui::text_editor;\n\
use gui::vertical_slider;\n\
use gui::mouse_area;\n\
use gui::keyboard_area;\n\
use gui::scrollable;\n\
use gui::combo_box;\n\
use gui::column";
async fn harness(widget_expr: &str) -> Result<InteractionHarness> {
let code = format!("{IMPORTS};\nlet result = {widget_expr}");
InteractionHarness::new(&code).await
}
const WIDGET_HIT: Point = Point::new(10.0, 10.0);
#[tokio::test(flavor = "current_thread")]
async fn button_click_produces_call() -> Result<()> {
let mut h = harness("button(#on_press: |_| null, &text(&\"Click me\"))").await?;
let msgs = h.click(WIDGET_HIT);
expect_call(&msgs);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn checkbox_click_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let checked = &false;\n\
let result = checkbox(#label: &\"Toggle me\", checked)"
);
let mut h = InteractionHarness::new(&code).await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn checkbox_toggle_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = checkbox(#label: &\"Toggle me\", #on_toggle: |v| null, &false)"
);
let mut h = InteractionHarness::new(&code).await?;
let msgs = h.click(WIDGET_HIT);
expect_call_with_args(&msgs, |args| {
matches!(args.iter().next(), Some(Value::Bool(_)))
});
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn toggler_click_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let toggled = &false;\n\
let result = toggler(#label: &\"Dark mode\", toggled)"
);
let mut h = InteractionHarness::new(&code).await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn toggler_toggle_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = toggler(#label: &\"Dark mode\", #on_toggle: |v| null, &false)"
);
let mut h = InteractionHarness::new(&code).await?;
let msgs = h.click(WIDGET_HIT);
expect_call_with_args(&msgs, |args| {
matches!(args.iter().next(), Some(Value::Bool(_)))
});
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn slider_click_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &50.0;\n\
let result = slider(#min: &0.0, #max: &100.0, val)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(200.0, 22.0)).await?;
let _ = h.click(Point::new(150.0, 10.0));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn slider_drag_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &50.0;\n\
let result = slider(#min: &0.0, #max: &100.0, val)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(200.0, 22.0)).await?;
let from = Point::new(100.0, 10.0);
let _ = h.drag_horizontal(from, 180.0, 5);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn slider_on_change_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let changed = false;\n\
let result = slider(#min: &0.0, #max: &100.0, \
#on_change: |v| changed <- v ~ true, &50.0)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(200.0, 22.0)).await?;
let initial = h.watch("test::changed").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(Point::new(150.0, 10.0));
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::changed"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn slider_on_release_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let released = false;\n\
let result = slider(#min: &0.0, #max: &100.0, \
#on_release: |click| released <- click ~ true, &50.0)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(200.0, 22.0)).await?;
let initial = h.watch("test::released").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(Point::new(150.0, 10.0));
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::released"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn vertical_slider_click_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &50.0;\n\
let result = vertical_slider(#min: &0.0, #max: &100.0, val)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(22.0, 200.0)).await?;
let _ = h.click(Point::new(10.0, 50.0));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn vertical_slider_on_change_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let changed = false;\n\
let result = vertical_slider(\
#min: &0.0, #max: &100.0, #on_change: |v| changed <- v ~ true, &50.0)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(22.0, 200.0)).await?;
let initial = h.watch("test::changed").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(Point::new(10.0, 50.0));
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::changed"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn vertical_slider_on_release_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let released = false;\n\
let result = vertical_slider(\
#min: &0.0, #max: &100.0, \
#on_release: |click| released <- click ~ true, &50.0)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(22.0, 200.0)).await?;
let initial = h.watch("test::released").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(Point::new(10.0, 50.0));
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::released"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn text_input_click_and_type_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &\"\";\n\
let result = text_input(#placeholder: &\"Type here\", val)"
);
let mut h = InteractionHarness::new(&code).await?;
h.click(WIDGET_HIT);
let _ = h.type_text("abc");
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn text_input_submit_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &\"\";\n\
let result = text_input(\
#placeholder: &\"Search\", \
#on_submit: |_| null, \
val)"
);
let mut h = InteractionHarness::new(&code).await?;
h.click(WIDGET_HIT);
h.type_text("query");
let msgs = h.press_key(iced_core::keyboard::key::Named::Enter);
expect_call(&msgs);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn text_input_on_input_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = text_input(\
#placeholder: &\"Type here\", \
#on_input: |s| null, \
&\"\")"
);
let mut h = InteractionHarness::new(&code).await?;
h.click(WIDGET_HIT);
let msgs = h.type_text("a");
expect_call_with_args(&msgs, |args| {
matches!(args.iter().next(), Some(Value::String(_)))
});
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn radio_click_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let sel = &\"none\";\n\
let result = radio(#label: &\"Option A\", #selected: sel, &\"option_a\")"
);
let mut h = InteractionHarness::new(&code).await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn radio_on_select_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = radio(\
#label: &\"Option A\", \
#selected: &\"none\", \
#on_select: |v| null, \
&\"option_a\")"
);
let mut h = InteractionHarness::new(&code).await?;
let msgs = h.click(WIDGET_HIT);
expect_call_with_args(&msgs, |args| {
matches!(args.iter().next(), Some(Value::String(_)))
});
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn pick_list_basic() -> Result<()> {
let mut h = harness(
"pick_list(\
#selected: &\"Red\",\
#placeholder: &\"Choose...\",\
&[\"Red\", \"Green\", \"Blue\"])",
)
.await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn pick_list_on_select_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = pick_list(\
#selected: &\"Red\", \
#on_select: |s| null, \
#placeholder: &\"Choose...\", \
&[\"Red\", \"Green\", \"Blue\"])"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(300.0, 200.0)).await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn mouse_area_press_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let pressed = false;\n\
let result = mouse_area(\
#on_press: |click| pressed <- click ~ true, \
&text(&\"Click zone\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::pressed").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(WIDGET_HIT);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::pressed"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn mouse_area_release_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let released = false;\n\
let result = mouse_area(\
#on_release: |click| released <- click ~ true, \
&text(&\"Click zone\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::released").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.click(WIDGET_HIT);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::released"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn text_editor_click_and_type_no_panic() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let val = &\"\";\n\
let result = text_editor(#placeholder: &\"Edit...\", val)"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(300.0, 100.0)).await?;
h.click(WIDGET_HIT);
let _ = h.type_text("hello");
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn text_editor_on_edit_produces_callback() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = text_editor(#placeholder: &\"Edit...\", #on_edit: |s| null, &\"\")"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(300.0, 100.0)).await?;
h.click(WIDGET_HIT);
let msgs = h.type_text("a");
let results = h.process_editor_actions(&msgs);
assert!(
results.iter().any(|(_, v)| matches!(v, Value::String(_))),
"text_editor on_edit should produce a String value callback"
);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn combo_box_on_select_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = combo_box(\
#selected: &\"Alpha\", \
#on_select: |s| null, \
#placeholder: &\"Pick one\", \
&[\"Alpha\", \"Beta\", \"Gamma\"])"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(300.0, 200.0)).await?;
let _ = h.view();
let _ = h.click(WIDGET_HIT);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn scrollable_on_scroll_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let result = scrollable(\
#on_scroll: |pos| null, \
#height: &`Fixed(50.0), \
&column(#spacing: &10.0, &[\
text(&\"Line 1\"), text(&\"Line 2\"), text(&\"Line 3\"), \
text(&\"Line 4\"), text(&\"Line 5\"), text(&\"Line 6\"), \
text(&\"Line 7\"), text(&\"Line 8\"), text(&\"Line 9\"), \
text(&\"Line 10\")\
]))"
);
let mut h = InteractionHarness::with_viewport(&code, Size::new(300.0, 50.0)).await?;
h.move_cursor(Point::new(10.0, 10.0));
let msgs = h.scroll(0.0, 3.0);
expect_call(&msgs);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn mouse_area_on_enter_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let entered = false;\n\
let result = mouse_area(\
#on_enter: |click| entered <- click ~ true, \
&text(&\"Zone\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::entered").await?;
assert_eq!(initial, Value::Bool(false));
let msgs = h.move_cursor(WIDGET_HIT);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::entered"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn mouse_area_on_exit_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let exited = false;\n\
let result = mouse_area(\
#on_exit: |click| exited <- click ~ true, \
&text(&\"Zone\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::exited").await?;
assert_eq!(initial, Value::Bool(false));
h.move_cursor(WIDGET_HIT);
let msgs = h.move_cursor(Point::new(999.0, 999.0));
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::exited"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn mouse_area_on_move_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let moved = false;\n\
let result = mouse_area(\
#on_move: |pos| moved <- pos ~ true, \
&text(&\"Zone\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::moved").await?;
assert_eq!(initial, Value::Bool(false));
h.move_cursor(Point::new(5.0, 5.0));
let msgs = h.move_cursor(WIDGET_HIT);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::moved"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn keyboard_area_on_key_press_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let pressed = false;\n\
let result = keyboard_area(\
#on_key_press: |ev| pressed <- ev ~ true, \
&text(&\"Type here\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::pressed").await?;
assert_eq!(initial, Value::Bool(false));
h.click(WIDGET_HIT);
let msgs = h.press_key(iced_core::keyboard::key::Named::Space);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::pressed"), Some(&Value::Bool(true)));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn keyboard_area_on_key_release_produces_call() -> Result<()> {
let code = format!(
"{IMPORTS};\n\
let released = false;\n\
let result = keyboard_area(\
#on_key_release: |ev| released <- ev ~ true, \
&text(&\"Type here\"))"
);
let mut h = InteractionHarness::new(&code).await?;
let initial = h.watch("test::released").await?;
assert_eq!(initial, Value::Bool(false));
h.click(WIDGET_HIT);
let msgs = h.release_key(iced_core::keyboard::key::Named::Space);
h.dispatch_calls(&msgs).await?;
assert_eq!(h.get_watched("test::released"), Some(&Value::Bool(true)));
Ok(())
}