Files
gtk-ahfail/crates/ahfail-gtklock/tests/ahfail_tests.rs
Asger Geel Weirsøe 8dd06377fc refactor: convert to Cargo workspace, move gtklock crate
Replaces the single-crate Cargo.toml with a workspace containing
ahfail-gtklock (migrated from root src/) and three stub crates
(ahfail-ui, ahfail-pam, ahfail-display). Updates meson.build to
build with -p ahfail-gtklock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 16:23:31 +02:00

299 lines
9.1 KiB
Rust

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();
}