use std::{
collections::HashMap,
sync::{Arc, Mutex},
thread,
time::Duration,
};
use crate::animation::{
backend::WindowBackend,
types::{AnimationError, Rect, Result, WindowRef},
};
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)] pub enum MockCall {
GetWindowRect(WindowRef),
ApplyBatch(Vec<(WindowRef, Rect)>),
DwmFlush,
}
#[derive(Debug, Default)]
#[allow(dead_code)] pub struct MockState {
pub calls: Vec<MockCall>,
pub rects: HashMap<WindowRef, Rect>,
pub apply_batch_result: Option<AnimationError>,
pub dwm_flush_delay_ms: u64,
}
#[allow(dead_code)] pub struct MockBackend {
state: Arc<Mutex<MockState>>,
}
#[allow(dead_code)] impl MockBackend {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(MockState::default())),
}
}
pub fn with_state(state: Arc<Mutex<MockState>>) -> Self {
Self { state }
}
pub fn state(&self) -> Arc<Mutex<MockState>> {
Arc::clone(&self.state)
}
}
impl Default for MockBackend {
fn default() -> Self {
Self::new()
}
}
impl WindowBackend for MockBackend {
fn get_window_rect(&self, window: WindowRef) -> Result<Rect> {
let mut state = self
.state
.lock()
.map_err(|_| AnimationError::Backend("mock state mutex poisoned".to_string()))?;
state.calls.push(MockCall::GetWindowRect(window));
state
.rects
.get(&window)
.copied()
.ok_or_else(|| AnimationError::Backend("window not found".to_string()))
}
fn apply_batch(&self, updates: &[(WindowRef, Rect)]) -> Result<()> {
let mut state = self
.state
.lock()
.map_err(|_| AnimationError::Backend("mock state mutex poisoned".to_string()))?;
state.calls.push(MockCall::ApplyBatch(updates.to_vec()));
match &state.apply_batch_result {
None => Ok(()),
Some(e) => Err(e.clone()),
}
}
fn dwm_flush(&self) -> Result<()> {
let delay_ms = {
let mut state = self
.state
.lock()
.map_err(|_| AnimationError::Backend("mock state mutex poisoned".to_string()))?;
state.calls.push(MockCall::DwmFlush);
state.dwm_flush_delay_ms
};
if delay_ms > 0 {
thread::sleep(Duration::from_millis(delay_ms));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_backend_has_empty_calls() {
let backend = MockBackend::new();
let state = backend.state.lock().unwrap();
assert!(state.calls.is_empty());
}
#[test]
fn get_window_rect_records_call_for_known_window() {
let state = Arc::new(Mutex::new(MockState {
rects: [(WindowRef(10), Rect::new(0, 0, 100, 200))].into(),
..MockState::default()
}));
let backend = MockBackend::with_state(Arc::clone(&state));
let rect = backend.get_window_rect(WindowRef(10)).unwrap();
assert_eq!(rect, Rect::new(0, 0, 100, 200));
let locked = state.lock().unwrap();
assert_eq!(locked.calls, vec![MockCall::GetWindowRect(WindowRef(10))]);
}
#[test]
fn get_window_rect_returns_err_for_unknown_window() {
let backend = MockBackend::new();
let result = backend.get_window_rect(WindowRef(99));
assert!(matches!(result, Err(AnimationError::Backend(_))));
}
#[test]
fn apply_batch_records_all_pairs() {
let backend = MockBackend::new();
let updates = vec![
(WindowRef(1), Rect::new(0, 0, 800, 600)),
(WindowRef(2), Rect::new(800, 0, 800, 600)),
];
backend.apply_batch(&updates).unwrap();
let state = backend.state.lock().unwrap();
assert_eq!(state.calls, vec![MockCall::ApplyBatch(updates)]);
}
#[test]
fn dwm_flush_records_the_call() {
let backend = MockBackend::new();
backend.dwm_flush().unwrap();
let state = backend.state.lock().unwrap();
assert_eq!(state.calls, vec![MockCall::DwmFlush]);
}
}