use crate::core::{ObjectId, PlatformFamily};
use crate::platform::{Platform, StubPlatform, WidgetTriggerEvent, WidgetTriggerKind};
#[cfg(feature = "harmony")]
#[test]
fn runtime_selects_harmony_backend_when_feature_enabled() {
let platform = crate::platform::get_platform();
assert_eq!(platform.backend_name(), "harmony-state-backend");
assert_eq!(platform.family(), PlatformFamily::Desktop);
}
#[test]
fn the_selected_backend_is_real_and_not_the_unknown_stub() {
let name = crate::platform::backend_name();
assert!(!name.is_empty(), "a backend must name itself");
let has_device_profile = cfg!(device_profile);
if has_device_profile {
assert_ne!(
name, "unknown-runtime-stub",
"a device build resolved no backend at all: one of the \
`create_native_platform` arms is missing its condition"
);
}
}
#[cfg(feature = "harmony")]
#[test]
fn runtime_does_not_fall_back_to_unknown_stub_with_harmony() {
let name = crate::platform::backend_name();
assert_ne!(name, "unknown-runtime-stub");
}
#[test]
fn consistency_menu_trigger_roundtrip() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let window = platform.create_window("w", 0, 0, 100, 100);
assert!(platform.inject_menu_trigger(window));
assert_eq!(platform.poll_menu_triggered(), Some(window));
}
#[test]
fn consistency_typed_widget_trigger_roundtrip() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let window = platform.create_window("w", 0, 0, 100, 100);
platform.register_widget(window, "btn", 0, 0, 80, 30);
assert!(platform.inject_widget_trigger_event(window, WidgetTriggerKind::Clicked));
assert_eq!(
platform.poll_widget_trigger_event(),
Some(WidgetTriggerEvent { widget_id: window, kind: WidgetTriggerKind::Clicked })
);
}
#[test]
fn consistency_compat_poll_widget_triggered_is_single_delivery_shim() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let window = platform.create_window("w", 0, 0, 100, 100);
assert!(platform.inject_widget_trigger_event(window, WidgetTriggerKind::Clicked));
assert_eq!(platform.poll_widget_triggered(), Some(window));
assert_eq!(platform.poll_widget_triggered(), None);
assert_eq!(platform.poll_widget_trigger_event(), None);
}
#[test]
fn consistency_list_box_data_path_roundtrip() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let list_box = platform.create_window("list", 0, 0, 120, 80);
assert!(platform.set_widget_selected_index(list_box, Some(1)));
assert_eq!(platform.widget_selected_index(list_box), Some(1));
assert!(platform.set_widget_selected_index(list_box, None));
assert_eq!(platform.widget_selected_index(list_box), None);
}
#[test]
fn consistency_combo_box_data_and_event_path_roundtrip() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let combo = platform.create_window("combo", 0, 0, 120, 24);
assert!(platform.set_widget_selected_index(combo, Some(1)));
assert_eq!(platform.widget_selected_index(combo), Some(1));
assert!(platform.inject_widget_trigger_event(combo, WidgetTriggerKind::SelectionChanged));
assert_eq!(
platform.poll_widget_trigger_event(),
Some(WidgetTriggerEvent { widget_id: combo, kind: WidgetTriggerKind::SelectionChanged })
);
}
#[test]
fn consistency_capability_contract_by_profile() {
let desktop = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let embedded = StubPlatform::new("test-embedded", PlatformFamily::Embedded);
assert!(desktop.native_capability_contract().is_some());
assert!(desktop.embedded_capability_contract().is_none());
assert!(embedded.native_capability_contract().is_none());
assert!(embedded.embedded_capability_contract().is_some());
}
#[test]
fn embedded_profile_self_drawn_controls_are_registered_with_the_host() {
let platform = StubPlatform::new("test-embedded", PlatformFamily::Embedded);
platform.register_widget(0x1001, "b", 0, 0, 80, 24);
platform.register_widget(0x1002, "c", 0, 0, 80, 24);
assert_eq!(platform.get_widget_text(0x1001), "b");
assert!(platform.is_widget_visible(0x1002));
assert!(platform.is_widget_enabled(0x1002));
assert_eq!(platform.widget_count(), 2);
assert!(platform.destroy_widget(0x1001));
assert_eq!(platform.widget_count(), 1);
}
#[test]
fn host_controls_are_explicitly_unsupported_on_every_profile() {
for family in [PlatformFamily::Desktop, PlatformFamily::Embedded] {
let platform = StubPlatform::new("test", family);
let window = platform.create_window("w", 0, 0, 200, 120);
assert_eq!(platform.create_menu_bar(window, 0, 0, 200, 24), 0);
assert_eq!(platform.create_menu(window, "File", 0, 0, 80, 24), 0);
assert_eq!(platform.menu_add_item(window, "Open", None), 0);
assert_eq!(platform.create_tool_bar(window, 0, 24, 200, 24), 0);
assert_eq!(platform.create_status_bar(window, "ready", 0, 96, 200, 24), 0);
assert_eq!(platform.create_button(window, "b", 0, 0, 80, 24), 0);
assert!(!platform.attach_menu_bar_to_window(window, window));
assert!(!platform.inject_menu_trigger(ObjectId::MAX));
}
}
#[test]
fn embedded_profile_selection_state_roundtrip() {
let platform = StubPlatform::new("test-embedded", PlatformFamily::Embedded);
let combo = platform.create_window("combo", 0, 0, 120, 24);
assert_ne!(combo, 0);
assert!(platform.set_widget_selected_index(combo, Some(1)));
assert_eq!(platform.widget_selected_index(combo), Some(1));
assert!(platform.inject_widget_trigger_event(combo, WidgetTriggerKind::SelectionChanged));
assert_eq!(
platform.poll_widget_trigger_event(),
Some(WidgetTriggerEvent { widget_id: combo, kind: WidgetTriggerKind::SelectionChanged })
);
let list = platform.create_window("list", 0, 30, 120, 80);
assert_ne!(list, 0);
assert!(platform.set_widget_selected_index(list, Some(0)));
assert_eq!(platform.widget_selected_index(list), Some(0));
assert!(platform.inject_widget_trigger_event(list, WidgetTriggerKind::SelectionChanged));
assert_eq!(
platform.poll_widget_trigger_event(),
Some(WidgetTriggerEvent { widget_id: list, kind: WidgetTriggerKind::SelectionChanged })
);
}
#[test]
fn stub_reports_unknown_platform_facts_honestly() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
assert_eq!(platform.total_memory_mb(), None);
assert_eq!(platform.process_memory_utilization(), None);
assert_eq!(platform.process_cpu_utilization(), None);
}
#[test]
fn battery_unknown_defaults_to_mains_power() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
assert!(!platform.is_on_battery());
}
#[test]
fn display_facts_default_to_the_honest_absence() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
assert!(platform.displays().is_empty());
assert_eq!(platform.display_of_window(0), None);
assert_eq!(platform.display_generation(), 0);
}
#[test]
fn stub_without_spooler_refuses_print_job() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
assert!(!platform.has_print_support());
let result = platform.spawn_print_job(std::path::Path::new("/tmp/does-not-matter.txt"));
assert!(result.is_err(), "a backend with no spooler must report the gap, not fake success");
}
#[test]
fn routing_resolves_every_kind_through_the_single_policy() {
use crate::control_backend::route_preference_for_widget_kind;
for kind in [
crate::widget::WidgetKind::ListBox,
crate::widget::WidgetKind::ScrollArea,
crate::widget::WidgetKind::SpinBox,
crate::widget::WidgetKind::Button,
] {
assert_eq!(
route_preference_for_widget_kind(kind),
crate::control_backend::ControlRoutePreference::CustomRequired,
"{kind:?} must route through the single painting backend"
);
}
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[test]
fn unix_print_probe_detects_cups_client_despite_nonzero_version_exit() {
let lp_exists = std::process::Command::new("lp").arg("--help").output().is_ok();
let lpr_exists = std::process::Command::new("lpr").arg("--help").output().is_ok();
if lp_exists || lpr_exists {
assert!(
crate::platform::types::unix_print_clients_available(),
"a spooler client exists on PATH but was reported missing"
);
}
}
#[test]
fn stub_reports_no_web_engine_capability() {
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
assert!(
!platform.supports_web_engine(),
"a backend that implements nothing must not claim a rendering engine"
);
}
#[test]
fn shortcut_style_follows_the_backend_not_a_middle_layer_cfg() {
use crate::shortcut::PlatformShortcutStyle;
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let expected = if cfg!(any(target_os = "macos", target_os = "ios")) {
PlatformShortcutStyle::Mac
} else {
PlatformShortcutStyle::Desktop
};
assert_eq!(platform.shortcut_style(), expected);
}
#[test]
fn format_shortcut_uses_the_backend_style() {
use crate::shortcut::{format_shortcut_for_platform, Key, Shortcut};
let platform = StubPlatform::new("test-desktop", PlatformFamily::Desktop);
let shortcut = Shortcut::primary(Key::Z);
assert_eq!(
platform.format_shortcut(&shortcut),
format_shortcut_for_platform(&shortcut, platform.shortcut_style()),
);
}
#[test]
fn every_backend_is_reported_at_the_mode_it_actually_has() {
use crate::platform::{runtime_gui_mode_for, RuntimeGuiMode};
struct Named(&'static str);
impl Platform for Named {
fn as_any(&self) -> &dyn core::any::Any {
self
}
fn backend_name(&self) -> &'static str {
self.0
}
fn family(&self) -> PlatformFamily {
PlatformFamily::Desktop
}
fn init(&self) {}
fn run(&self) {}
fn quit(&self) -> bool {
true
}
fn create_window(&self, _t: &str, _x: i32, _y: i32, _w: u32, _h: u32) -> ObjectId {
0
}
}
for name in ["cocoa", "WindowsPlatform"] {
assert_eq!(
runtime_gui_mode_for(&Named(name)),
RuntimeGuiMode::NativeInteractive,
"{name} opens native windows, so it must be reported interactive regardless of what \
its capabilities() say about DPI — those are separate questions"
);
}
let expected_gtk = if cfg!(all(target_os = "linux", feature = "gtk-native")) {
RuntimeGuiMode::NativeInteractive
} else {
RuntimeGuiMode::PreviewOrStub
};
assert_eq!(runtime_gui_mode_for(&Named("gtk")), expected_gtk);
let expected_wayland = if cfg!(all(target_os = "linux", feature = "wayland-native")) {
RuntimeGuiMode::NativeInteractive
} else {
RuntimeGuiMode::PreviewOrStub
};
assert_eq!(runtime_gui_mode_for(&Named("wayland")), expected_wayland);
for name in [
"macos-objc2-preview",
"macos-fallback-stub",
"unknown-runtime-stub",
"harmony-state-backend",
"android-state-backend",
"android-mobile",
"ios-state-backend",
"wasm-state-backend",
"portable",
"recording-test-backend",
"harmony-desktop",
] {
assert_eq!(
runtime_gui_mode_for(&Named(name)),
RuntimeGuiMode::PreviewOrStub,
"{name} creates no native window, so claiming it will open one is the silent \
failure this function exists to prevent"
);
}
let stub = StubPlatform::new("portable", PlatformFamily::Embedded);
assert_eq!(runtime_gui_mode_for(&stub), RuntimeGuiMode::PreviewOrStub);
}
#[test]
fn a_declared_typed_trigger_capability_is_backed_by_the_queue_that_delivers_it() {
use crate::platform::PlatformCapabilities;
fn assert_delivers(name: &str, platform: &dyn Platform) {
let caps: PlatformCapabilities = platform.capabilities();
if !caps.typed_widget_trigger {
return;
}
let window = platform.create_window("trigger-probe", 0, 0, 100, 100);
assert!(
platform.inject_widget_trigger_event(window, WidgetTriggerKind::Clicked),
"{name}: declares `typed_widget_trigger: true` but `inject_widget_trigger_event` \
refused a window it just created, so the flag promises a queue this backend does \
not delegate to"
);
assert_eq!(
platform.poll_widget_trigger_event(),
Some(WidgetTriggerEvent { widget_id: window, kind: WidgetTriggerKind::Clicked }),
"{name}: the injected event must come back out of the typed view"
);
assert_eq!(
platform.poll_widget_triggered(),
None,
"{name}: and both views must be one queue, so the typed poll already consumed it"
);
}
assert_delivers("stub", &StubPlatform::new("trigger-stub", PlatformFamily::Desktop));
#[cfg(target_os = "linux")]
assert_delivers("linux", &crate::platform::linux::LinuxPlatform::new());
#[cfg(all(target_os = "linux", feature = "wayland-native"))]
assert_delivers("wayland", &crate::platform::wayland::WaylandPlatform::new());
#[cfg(all(target_os = "macos", feature = "cocoa-legacy"))]
assert_delivers("cocoa", &crate::platform::macos::MacOSPlatform::default());
#[cfg(all(target_os = "macos", any(feature = "macos", feature = "cocoa-legacy")))]
assert_delivers("macos-objc2", &crate::platform::macos_objc2::MacOSObjc2Platform::default());
#[cfg(target_os = "windows")]
assert_delivers("windows", &crate::platform::windows::WindowsPlatform::new());
#[cfg(feature = "wasm")]
assert_delivers("wasm", &crate::platform::wasm::WasmPlatform::default());
#[cfg(feature = "harmony")]
assert_delivers("harmony", &crate::platform::harmony::HarmonyPlatform::new());
}
#[test]
fn capability_flags_agree_with_the_methods_they_promise() {
use crate::platform::PlatformCapabilities;
fn assert_agrees(name: &str, platform: &dyn Platform) {
let caps: PlatformCapabilities = platform.capabilities();
if caps.ime {
assert!(
platform.ime_bridge().is_some(),
"{name}: claims `ime: true` but `ime_bridge()` answers None, so the flag \
promises an input-method client that does not exist"
);
}
if caps.accessibility {
assert!(
platform.accessibility_bridge().is_some(),
"{name}: claims `accessibility: true` but `accessibility_bridge()` answers None"
);
}
if caps.clipboard {
assert!(
platform.clipboard_backend().is_some(),
"{name}: claims `clipboard: true` but `clipboard_backend()` answers None, so the \
flag promises a host clipboard this backend cannot hand out"
);
}
if caps.dpi_scaling {
let scale = platform.dpi_scale_factor();
assert!(
scale.is_finite() && scale > 0.0,
"{name}: claims `dpi_scaling: true` but reports a scale of {scale}, which no \
layout can use"
);
}
}
assert_agrees("stub", &StubPlatform::new("flags-stub", PlatformFamily::Desktop));
#[cfg(target_os = "linux")]
assert_agrees("linux", &crate::platform::linux::LinuxPlatform::new());
#[cfg(all(target_os = "linux", feature = "wayland-native"))]
assert_agrees("wayland", &crate::platform::wayland::WaylandPlatform::new());
#[cfg(all(target_os = "macos", feature = "cocoa-legacy"))]
assert_agrees("cocoa", &crate::platform::macos::MacOSPlatform::default());
#[cfg(all(target_os = "macos", any(feature = "macos", feature = "cocoa-legacy")))]
assert_agrees("macos-objc2", &crate::platform::macos_objc2::MacOSObjc2Platform::default());
#[cfg(target_os = "windows")]
assert_agrees("windows", &crate::platform::windows::WindowsPlatform::new());
#[cfg(feature = "wasm")]
assert_agrees("wasm", &crate::platform::wasm::WasmPlatform::default());
#[cfg(feature = "harmony")]
assert_agrees("harmony", &crate::platform::harmony::HarmonyPlatform::new());
#[cfg(target_os = "android")]
assert_agrees("android", &crate::platform::android::AndroidPlatform::new());
#[cfg(target_os = "ios")]
assert_agrees("ios", &crate::platform::ios::IosMobilePlatform::new());
}
use crate::compat::atomic::{AtomicBool, Ordering};
struct LoopingPlatform {
ticking: AtomicBool,
running: AtomicBool,
}
impl LoopingPlatform {
const fn new() -> Self {
Self { ticking: AtomicBool::new(false), running: AtomicBool::new(false) }
}
}
impl Platform for LoopingPlatform {
fn as_any(&self) -> &dyn core::any::Any {
self
}
fn backend_name(&self) -> &'static str {
"looping-test-backend"
}
fn family(&self) -> PlatformFamily {
PlatformFamily::Desktop
}
fn init(&self) {}
fn run(&self) {
self.running.store(true, Ordering::SeqCst);
while self.running.load(Ordering::SeqCst) {
crate::drive_frame(1);
self.ticking.store(true, Ordering::SeqCst);
std::thread::yield_now();
}
}
fn quit(&self) -> bool {
self.running.store(false, Ordering::SeqCst);
true
}
fn create_window(&self, _t: &str, _x: i32, _y: i32, _w: u32, _h: u32) -> ObjectId {
0
}
}
static LOOP_SEAM_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn a_request_from_another_thread_ends_a_running_loop() {
use std::sync::mpsc;
use std::time::{Duration, Instant};
let _serial = LOOP_SEAM_SERIAL.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
static LOOP_PLATFORM: LoopingPlatform = LoopingPlatform::new();
let loop_thread = std::thread::spawn(|| {
crate::platform::with_platform(&LOOP_PLATFORM, || crate::platform::get_platform().run())
});
let deadline = Instant::now() + Duration::from_secs(10);
while !LOOP_PLATFORM.ticking.load(Ordering::SeqCst) {
assert!(
Instant::now() < deadline,
"the loop never reached its first frame, so nothing could read a request"
);
std::thread::sleep(Duration::from_millis(1));
}
assert!(
crate::platform::quit(),
"a running loop reads the request at its next frame, so this must answer `true`; \
answering `false` tells a host its session will not end, and it will kill the process"
);
let (returned_tx, returned_rx) = mpsc::channel();
std::thread::spawn(move || {
let _ = loop_thread.join();
let _ = returned_tx.send(());
});
assert!(
returned_rx.recv_timeout(Duration::from_secs(10)).is_ok(),
"the loop was asked to stop and did not return, which is the defect this test exists for"
);
assert!(
!LOOP_PLATFORM.running.load(Ordering::SeqCst),
"the loop returned but its exit condition is still set, so something else ended it"
);
}
#[test]
fn work_scheduled_from_another_thread_runs_on_the_loops_thread() {
use std::sync::atomic::AtomicU64;
use std::time::{Duration, Instant};
let _serial = LOOP_SEAM_SERIAL.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
static LOOP_PLATFORM: LoopingPlatform = LoopingPlatform::new();
static RAN_ON: AtomicU64 = AtomicU64::new(0);
static RAN: AtomicBool = AtomicBool::new(false);
static LOOP_THREAD: AtomicU64 = AtomicU64::new(0);
const DELAY_MS: u64 = 120;
let loop_thread = std::thread::spawn(|| {
crate::platform::with_platform(&LOOP_PLATFORM, || {
LOOP_THREAD.store(thread_id_as_u64(), Ordering::SeqCst);
crate::platform::get_platform().run();
})
});
let deadline = Instant::now() + Duration::from_secs(10);
while !LOOP_PLATFORM.ticking.load(Ordering::SeqCst) {
assert!(Instant::now() < deadline, "the loop never reached its first frame");
std::thread::sleep(Duration::from_millis(1));
}
let scheduled = crate::schedule_on_main(Duration::from_millis(DELAY_MS), || {
RAN_ON.store(thread_id_as_u64(), Ordering::SeqCst);
RAN.store(true, Ordering::SeqCst);
let _ = crate::platform::get_platform().quit();
});
assert!(
scheduled,
"a running loop reads the queue at its next frame, so this must answer `true`"
);
let asked_at = Instant::now();
let (returned_tx, returned_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let _ = loop_thread.join();
let _ = returned_tx.send(());
});
assert!(
returned_rx.recv_timeout(Duration::from_secs(10)).is_ok(),
"the loop must return once the scheduled callback stopped it"
);
let after = asked_at.elapsed();
assert!(RAN.load(Ordering::SeqCst), "the scheduled work never ran");
assert_eq!(
RAN_ON.load(Ordering::SeqCst),
LOOP_THREAD.load(Ordering::SeqCst),
"the work ran on a thread that does not own the loop, so a toolkit call inside it would \
be a cross-thread call"
);
assert!(
after >= Duration::from_millis(DELAY_MS),
"the work ran before its deadline ({after:?} < {DELAY_MS} ms)"
);
assert!(
!LOOP_PLATFORM.running.load(Ordering::SeqCst),
"the loop is still marked as running, so the callback did not stop it"
);
}
fn thread_id_as_u64() -> u64 {
format!("{:?}", std::thread::current().id())
.rsplit_once('(')
.and_then(|(_, tail)| tail.trim_end_matches(')').parse().ok())
.unwrap_or(0)
}