use crate::platform::wayland::WaylandPlatform;
use crate::platform::Platform;
#[cfg(all(feature = "wayland-native", target_os = "linux"))]
#[test]
fn native_session_binds_live_compositor() {
use crate::core::PlatformFamily;
use crate::platform::Platform as _;
if !compositor_advertised() {
eprintln!("skipping: WAYLAND_DISPLAY is not set (no compositor to bind)");
return;
}
let backend = WaylandPlatform::new();
backend.init();
assert_eq!(backend.family(), PlatformFamily::Desktop);
let window = backend.create_window("NativeProbe", 0, 0, 320, 200);
assert!(window > 0, "window should be created");
let guard = backend.native_session.lock().expect("native_session mutex poisoned");
let session = guard.as_ref().expect(
"native_session must be Some after create_window with a live compositor \
(state-only fallback would leave it None)",
);
assert!(
session.state.compositor.is_some(),
"wl_compositor must be bound from the live compositor"
);
assert!(
session.state.xdg_wm_base.is_some(),
"xdg_wm_base must be bound from the live compositor"
);
eprintln!(
"bound live compositor: wl_compositor=yes xdg_wm_base=yes dpi_scale={}",
session.state.dpi_scale
);
}
#[cfg(all(feature = "wayland-native", target_os = "linux"))]
fn compositor_advertised() -> bool {
matches!(std::env::var("WAYLAND_DISPLAY"), Ok(v) if !v.is_empty())
}
#[cfg(all(feature = "wayland-native", target_os = "linux"))]
#[test]
fn native_session_stays_empty_without_compositor() {
use crate::platform::Platform as _;
if compositor_advertised() {
eprintln!("skipping: a compositor is advertised, cannot test the fallback");
return;
}
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("Fallback", 0, 0, 200, 120);
assert!(window > 0, "state-only window should still be created");
let guard = backend.native_session.lock().expect("native_session mutex poisoned");
assert!(guard.is_none(), "native_session must stay empty when there is no compositor");
}
#[test]
fn run_exits_promptly_on_quit_without_compositor() {
use std::sync::Arc;
use std::time::{Duration, Instant};
let backend = Arc::new(WaylandPlatform::new());
backend.init();
let runner = Arc::clone(&backend);
let handle = std::thread::spawn(move || runner.run());
std::thread::sleep(Duration::from_millis(50));
let started = Instant::now();
backend.quit();
let mut finished = false;
for _ in 0..100 {
if handle.is_finished() {
finished = true;
break;
}
std::thread::sleep(Duration::from_millis(20));
}
let elapsed = started.elapsed();
if !finished {
panic!("run() did not exit within 2s of quit() (elapsed {elapsed:?})");
}
handle.join().expect("event loop thread panicked");
assert!(elapsed < Duration::from_millis(500), "quit() took too long: {elapsed:?}");
}
#[test]
fn platform_creates_and_runs() {
let backend = WaylandPlatform::new();
backend.init();
assert_eq!(backend.backend_name(), "wayland");
let window = backend.create_window("TestWindow", 50, 50, 400, 300);
assert!(window > 0, "Window should be created");
}
#[test]
fn host_creates_no_controls() {
let backend = WaylandPlatform::new();
backend.init();
assert!(
!backend.capabilities().native_menu,
"the Wayland menu is an in-process tree, not a compositor menu"
);
let window = backend.create_window("NoControls", 0, 0, 400, 300);
assert!(window > 0, "Window should be created");
assert_eq!(backend.create_button(window, "Click", 10, 10, 80, 24), 0);
assert_eq!(backend.create_label(window, "Hello", 10, 40, 80, 24), 0);
assert_eq!(backend.create_checkbox(window, "Check", 10, 70, 80, 24), 0);
assert_eq!(backend.create_line_edit(window, "edit", 10, 100, 160, 24), 0);
assert_eq!(backend.create_combo_box(window, 10, 220, 140, 24), 0);
assert_eq!(backend.create_list_box(window, 10, 250, 140, 80), 0);
assert_eq!(backend.create_spin_box(window, 10, 10, 80, 24), 0);
assert_eq!(backend.create_message_box(window, "Title", "Body", 10, 10, 300, 150), 0);
}
#[test]
fn window_lifecycle() {
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("Lifecycle", 0, 0, 200, 120);
assert!(window > 0, "Window should be created");
backend.set_widget_text(window, "UpdatedTitle");
assert_eq!(backend.get_widget_text(window), "UpdatedTitle");
backend.show_widget(window);
assert!(backend.is_widget_visible(window), "Window should be visible");
backend.hide_widget(window);
assert!(!backend.is_widget_visible(window), "Window should be hidden");
backend.set_widget_enabled(window, false);
assert!(!backend.is_widget_enabled(window), "Window should be disabled");
backend.set_widget_geometry(window, 20, 20, 320, 240);
backend.set_widget_accessibility_name(window, "MainWindow");
assert_eq!(backend.get_widget_accessibility_name(window), "MainWindow");
assert!(backend.destroy_widget(window), "Window should be destroyed");
assert!(!backend.destroy_widget(window), "A destroyed window cannot be destroyed twice");
}
#[test]
fn clipboard_roundtrip() {
let backend = WaylandPlatform::new();
backend.init();
let _window = backend.create_window("ClipTest", 0, 0, 200, 120);
assert!(backend.set_clipboard_text("wayland_clip_test"), "Should set clipboard text");
assert_eq!(backend.get_clipboard_text(), "wayland_clip_test", "Clipboard text should match");
assert!(backend.set_clipboard_text("updated_clip"), "Should overwrite clipboard");
assert_eq!(backend.get_clipboard_text(), "updated_clip", "Updated clipboard text should match");
assert!(backend.set_clipboard_text(""), "Should set empty clipboard");
assert_eq!(backend.get_clipboard_text(), "", "Empty clipboard should match");
}
#[test]
fn menu_system() {
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("MenuTest", 0, 0, 400, 300);
let menu_bar = backend.create_menu_bar(window, 0, 0, 400, 24);
assert!(menu_bar > 0, "MenuBar should be created");
let file_menu = backend.create_menu(menu_bar, "File", 0, 24, 100, 24);
assert!(file_menu > 0, "File menu should be created");
let new_item = backend.menu_add_item(file_menu, "New", Some("Ctrl+N"));
assert!(new_item > 0, "New menu item should be created");
let open_item = backend.menu_add_item(file_menu, "Open", Some("Ctrl+O"));
assert!(open_item > 0, "Open menu item should be created");
let quit_item = backend.menu_add_item(file_menu, "Quit", Some("Ctrl+Q"));
assert!(quit_item > 0, "Quit menu item should be created");
assert!(
backend.attach_menu_bar_to_window(window, menu_bar),
"Should attach menu bar to window"
);
assert!(backend.inject_menu_trigger(open_item), "Should inject menu trigger");
assert_eq!(backend.poll_menu_triggered(), Some(open_item), "Should poll open item");
assert!(backend.inject_menu_trigger(quit_item), "Should inject quit trigger");
assert_eq!(backend.poll_menu_triggered(), Some(quit_item), "Should poll quit item");
}
#[test]
fn widget_trigger_events() {
use crate::platform::WidgetTriggerKind;
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("TriggerTest", 0, 0, 200, 120);
assert!(
backend.inject_widget_trigger_event(window, WidgetTriggerKind::Clicked),
"Should inject click event"
);
let event = backend.poll_widget_trigger_event();
assert!(event.is_some(), "Should poll trigger event");
assert_eq!(event.unwrap().widget_id, window);
assert_eq!(event.unwrap().kind, WidgetTriggerKind::Clicked, "Should match Clicked kind");
assert_eq!(backend.poll_widget_triggered(), None, "The queue must be drained");
}
#[test]
fn invalid_parent_and_kind_validation() {
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("Validation", 0, 0, 400, 300);
assert!(window > 0);
let bogus = 9999;
assert_eq!(backend.create_menu(window, "File", 0, 0, 10, 10), 0);
let menu_bar = backend.create_menu_bar(window, 0, 0, 400, 24);
assert!(menu_bar > 0);
let menu = backend.create_menu(menu_bar, "File", 0, 0, 10, 10);
assert!(menu > 0, "menu under a menu bar must succeed");
assert_eq!(backend.menu_add_item(window, "Bad", None), 0);
let item = backend.menu_add_item(menu, "Open", None);
assert!(item > 0);
assert!(!backend.inject_menu_trigger(window));
assert!(!backend.inject_menu_trigger(menu_bar));
assert!(backend.inject_menu_trigger(item));
assert!(!backend.attach_menu_bar_to_window(bogus, menu_bar));
assert!(!backend.attach_menu_bar_to_window(window, bogus));
assert!(!backend.attach_menu_bar_to_window(window, item));
assert!(backend.attach_menu_bar_to_window(window, menu_bar));
use crate::platform::WidgetTriggerKind;
assert!(!backend.inject_widget_trigger_event(bogus, WidgetTriggerKind::Clicked));
assert!(backend.inject_widget_trigger_event(window, WidgetTriggerKind::Clicked));
assert!(backend.destroy_widget(menu_bar));
assert_eq!(backend.create_menu(window, "File", 0, 0, 10, 10), 0);
}
#[test]
fn drag_and_drop() {
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("DragTest", 0, 0, 400, 300);
assert!(backend.begin_drag(window, "text/plain", b"drag payload"), "Should begin drag");
let drop = backend.poll_drop_event();
assert!(drop.is_some(), "Should poll drop event");
let drop = drop.unwrap();
assert_eq!(drop.source_widget_id, window);
assert_eq!(drop.mime, "text/plain");
assert_eq!(drop.payload, b"drag payload");
}
#[test]
fn ime_and_accessibility() {
let backend = WaylandPlatform::new();
backend.init();
let window = backend.create_window("IMETest", 0, 0, 400, 300);
assert!(backend.set_widget_ime_enabled(window, true), "Should enable IME");
assert!(backend.is_widget_ime_enabled(window), "IME should be enabled");
assert!(backend.set_widget_ime_enabled(window, false), "Should disable IME");
assert!(!backend.is_widget_ime_enabled(window), "IME should be disabled");
assert!(
backend.set_widget_accessibility_name(window, "InputField"),
"Should set accessibility name"
);
assert_eq!(
backend.get_widget_accessibility_name(window),
"InputField",
"Accessibility name should match"
);
}