use ahfail_module::*; use gtk::prelude::*; use gtk::{glib, gdk_pixbuf, gdk}; use gstreamer as gst; use std::ptr; use std::ffi::c_void; use gtk::glib::translate::{ToGlibPtr, Stash}; // Mock implementation of the resource getter for tests #[no_mangle] extern "C" fn ahfail_get_resource() -> *mut gtk::gio::ffi::GResource { ptr::null_mut() } // Helper struct to simulate the C flexible array member allocation #[repr(C)] struct WindowWithStorage { window: Window, // Reserve space for the 'module_data' flexible array (1 pointer) storage: *mut c_void, } fn setup_test_environment() { static ONCE: std::sync::Once = std::sync::Once::new(); ONCE.call_once(|| { let _ = gtk::init(); let _ = gst::init(); }); } fn flush_events() { while gtk::events_pending() { gtk::main_iteration(); } } fn create_mock_context() -> (WindowWithStorage, gtk::Window, gtk::Label, gtk::Overlay, gdk::Display) { setup_test_environment(); let error_label = gtk::Label::new(Some("")); let overlay = gtk::Overlay::new(); let window_widget = gtk::Window::new(gtk::WindowType::Toplevel); let display = gdk::Display::default().expect("No display available for testing"); let monitor = display.monitor(0).expect("No monitor available"); // Explicitly type the Stash to avoid ambiguity let monitor_stash: Stash<*mut gdk::ffi::GdkMonitor, _> = monitor.to_glib_none(); let window_stash: Stash<*mut gtk::ffi::GtkWindow, _> = window_widget.to_glib_none(); let overlay_stash: Stash<*mut gtk::ffi::GtkOverlay, _> = overlay.to_glib_none(); let label_stash: Stash<*mut gtk::ffi::GtkLabel, _> = error_label.to_glib_none(); let win = Window { monitor: monitor_stash.0, window: window_stash.0 as *mut gtk::ffi::GtkWidget, overlay: overlay_stash.0, window_box: ptr::null_mut(), body_revealer: ptr::null_mut(), body_grid: ptr::null_mut(), input_label: ptr::null_mut(), input_field: ptr::null_mut(), message_revealer: ptr::null_mut(), message_scrolled_window: ptr::null_mut(), message_box: ptr::null_mut(), unlock_button: ptr::null_mut(), error_label: label_stash.0 as *mut gtk::ffi::GtkWidget, warning_label: ptr::null_mut(), info_box: ptr::null_mut(), time_box: ptr::null_mut(), clock_label: ptr::null_mut(), date_label: ptr::null_mut(), module_data: __IncompleteArrayField::new(), }; let storage = WindowWithStorage { window: win, storage: ptr::null_mut(), }; (storage, window_widget, error_label, overlay, display) } fn inject_test_state() { let pixbuf = gdk_pixbuf::Pixbuf::new(gdk_pixbuf::Colorspace::Rgb, false, 8, 1, 1).unwrap(); let anim = gdk_pixbuf::PixbufSimpleAnim::new(1, 1, 1.0); anim.add_frame(&pixbuf); // Use a minimal valid WAV (silent) to avoid GStreamer errors let wav_base64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQEAAAAA"; let data_uri = format!("data:audio/wav;base64,{}", wav_base64); MODULE_STATE.with(|state| { let mut state = state.borrow_mut(); state.animation = Some(anim.upcast()); state.audio_uri = Some(data_uri); }); } fn run_test_01_initialization() { unsafe { on_activation(ptr::null_mut(), 0); } MODULE_STATE.with(|state| { let state = state.borrow(); // It should have set the audio URI to the default assert_eq!(state.audio_uri.as_deref(), Some("resource:///ahfail/audio/magic-word.mp3")); }); } fn run_test_02_window_create_null() { unsafe { on_window_create(ptr::null_mut(), ptr::null_mut()); } } fn run_test_03_window_create_valid() { let (mut win_storage, _w, _l, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; unsafe { on_window_create(ptr::null_mut(), ctx_ptr); let data = get_window_data_ref(ctx_ptr); assert!(data.is_some(), "WindowData should be initialized"); on_window_destroy(ptr::null_mut(), ctx_ptr); flush_events(); } } fn run_test_04_trigger_effect_sprite() { let (mut win_storage, _w, label, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx_ptr); label.set_text("Error"); flush_events(); let data = get_window_data_ref(ctx_ptr).unwrap(); assert_eq!(data.sprites.len(), 1); on_window_destroy(ptr::null_mut(), ctx_ptr); flush_events(); } } fn run_test_05_trigger_effect_audio() { let (mut win_storage, _w, label, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx_ptr); label.set_text("Error"); flush_events(); let data = get_window_data_ref(ctx_ptr).unwrap(); assert_eq!(data.active_players.len(), 1); on_window_destroy(ptr::null_mut(), ctx_ptr); flush_events(); } } fn run_test_06_multiple_triggers() { let (mut win_storage, _w, label, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx_ptr); for _ in 0..5 { label.set_text("Error"); flush_events(); } let data = get_window_data_ref(ctx_ptr).unwrap(); assert_eq!(data.sprites.len(), 5); assert_eq!(data.active_players.len(), 5); on_window_destroy(ptr::null_mut(), ctx_ptr); flush_events(); } } fn run_test_07_window_cleanup() { let (mut win_storage, _w, label, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx_ptr); label.set_text("Error"); flush_events(); assert!(get_window_data_ref(ctx_ptr).is_some()); on_window_destroy(ptr::null_mut(), ctx_ptr); assert!(get_window_data_ref(ctx_ptr).is_none()); flush_events(); } } fn run_test_08_multiple_windows() { let (mut win1, _w1, label1, _o1, _) = create_mock_context(); let (mut win2, _w2, label2, _o2, _) = create_mock_context(); let ctx1 = &mut win1.window as *mut Window; let ctx2 = &mut win2.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx1); on_window_create(ptr::null_mut(), ctx2); label1.set_text("E1"); flush_events(); label2.set_text("E2"); label2.set_text("E2 again"); flush_events(); let data1 = get_window_data_ref(ctx1).unwrap(); let data2 = get_window_data_ref(ctx2).unwrap(); assert_eq!(data1.sprites.len(), 1); assert_eq!(data2.sprites.len(), 2); on_window_destroy(ptr::null_mut(), ctx1); on_window_destroy(ptr::null_mut(), ctx2); flush_events(); } } fn run_test_09_idle_hide_cleanup() { let (mut win_storage, _w, label, _o, _) = create_mock_context(); let ctx_ptr = &mut win_storage.window as *mut Window; inject_test_state(); unsafe { on_window_create(ptr::null_mut(), ctx_ptr); label.set_text("Error"); flush_events(); // Mock GtkLock struct let mut windows_array = glib::ffi::g_array_new(0, 0, std::mem::size_of::<*mut Window>() as u32); glib::ffi::g_array_append_vals(windows_array, &ctx_ptr as *const _ as *const c_void, 1); let mut lock = GtkLock { windows: windows_array, }; on_idle_hide(&mut lock); assert!(get_window_data_ref(ctx_ptr).is_none()); glib::ffi::g_array_free(windows_array, 1); flush_events(); } } fn run_test_10_module_unload() { inject_test_state(); MODULE_STATE.with(|state| { assert!(state.borrow().audio_uri.is_some()); }); unsafe { g_module_unload(ptr::null_mut()); } MODULE_STATE.with(|state| { let state = state.borrow(); assert!(state.animation.is_none()); assert!(state.audio_uri.is_none()); }); } #[test] fn run_all_tests_sequentially() { println!("Running test 01..."); run_test_01_initialization(); println!("Running test 02..."); run_test_02_window_create_null(); println!("Running test 03..."); run_test_03_window_create_valid(); println!("Running test 04..."); run_test_04_trigger_effect_sprite(); println!("Running test 05..."); run_test_05_trigger_effect_audio(); println!("Running test 06..."); run_test_06_multiple_triggers(); println!("Running test 07..."); run_test_07_window_cleanup(); println!("Running test 08..."); run_test_08_multiple_windows(); println!("Running test 09..."); run_test_09_idle_hide_cleanup(); println!("Running test 10..."); run_test_10_module_unload(); // Final flush flush_events(); }