use crate::{
core::{Cmd, Model},
event::Event,
};
use std::collections::VecDeque;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct EventTestHarness<M: Model> {
model: M,
event_queue: VecDeque<Event<M::Message>>,
processed_events: Vec<Event<M::Message>>,
update_count: AtomicUsize,
has_quit: bool,
}
impl<M: Model> EventTestHarness<M>
where
M::Message: Clone,
{
pub fn new(mut model: M) -> Self {
let init_cmd = model.init();
let mut harness = Self {
model,
event_queue: VecDeque::new(),
processed_events: Vec::new(),
update_count: AtomicUsize::new(0),
has_quit: false,
};
if !init_cmd.is_noop() {
harness.execute_command(init_cmd);
}
harness
}
pub fn send_event(&mut self, event: Event<M::Message>) {
self.event_queue.push_back(event);
}
pub fn send_events(&mut self, events: impl IntoIterator<Item = Event<M::Message>>) {
self.event_queue.extend(events);
}
pub fn process_one(&mut self) -> Option<Event<M::Message>> {
if self.has_quit {
return None;
}
if let Some(event) = self.event_queue.pop_front() {
self.update_count.fetch_add(1, Ordering::SeqCst);
let event_clone = event.clone();
let cmd = self.model.update(event);
self.execute_command(cmd);
self.processed_events.push(event_clone.clone());
Some(event_clone)
} else {
None
}
}
pub fn process_all(&mut self) -> Vec<Event<M::Message>> {
let mut processed = Vec::new();
while let Some(event) = self.process_one() {
processed.push(event);
}
processed
}
pub fn process_until<F>(&mut self, mut condition: F) -> bool
where
F: FnMut(&M) -> bool,
{
while !self.has_quit && !self.event_queue.is_empty() {
if condition(&self.model) {
return true;
}
self.process_one();
}
condition(&self.model)
}
fn execute_command(&mut self, cmd: Cmd<M::Message>) {
if cmd.is_quit() {
self.has_quit = true;
} else if !cmd.is_noop() {
self.event_queue.push_back(Event::Tick);
}
}
pub fn model(&self) -> &M {
&self.model
}
pub fn model_mut(&mut self) -> &mut M {
&mut self.model
}
pub fn update_count(&self) -> usize {
self.update_count.load(Ordering::SeqCst)
}
pub fn has_quit(&self) -> bool {
self.has_quit
}
pub fn processed_events(&self) -> &[Event<M::Message>] {
&self.processed_events
}
pub fn queue_len(&self) -> usize {
self.event_queue.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TestPriority {
Critical = 0,
High = 1,
Normal = 2,
Low = 3,
}
pub struct PriorityEventTestHarness<M: Model> {
model: M,
priority_queue: VecDeque<(Event<M::Message>, TestPriority)>,
processed_events: Vec<(Event<M::Message>, TestPriority)>,
update_count: AtomicUsize,
has_quit: bool,
}
impl<M: Model> PriorityEventTestHarness<M>
where
M::Message: Clone + Send + 'static,
{
pub fn new(mut model: M) -> Self {
let init_cmd = model.init();
let mut harness = Self {
model,
priority_queue: VecDeque::new(),
processed_events: Vec::new(),
update_count: AtomicUsize::new(0),
has_quit: false,
};
if !init_cmd.is_noop() {
harness.execute_command(init_cmd);
}
harness
}
pub fn send_with_priority(&mut self, event: Event<M::Message>, priority: TestPriority) {
let insert_pos = self
.priority_queue
.iter()
.position(|(_, p)| *p > priority)
.unwrap_or(self.priority_queue.len());
self.priority_queue.insert(insert_pos, (event, priority));
}
pub fn process_one(&mut self) -> Option<(Event<M::Message>, TestPriority)> {
if self.has_quit {
return None;
}
if let Some((event, priority)) = self.priority_queue.pop_front() {
self.update_count.fetch_add(1, Ordering::SeqCst);
let event_clone = event.clone();
let cmd = self.model.update(event);
self.execute_command(cmd);
self.processed_events.push((event_clone.clone(), priority));
Some((event_clone, priority))
} else {
None
}
}
pub fn process_all_and_verify_priority(&mut self) -> bool {
let mut last_priority = TestPriority::Critical;
while let Some((_, priority)) = self.process_one() {
if priority as u8 > last_priority as u8 {
return false;
}
last_priority = priority;
}
true
}
fn execute_command(&mut self, cmd: Cmd<M::Message>) {
if cmd.is_quit() {
self.has_quit = true;
} else if !cmd.is_noop() {
self.send_with_priority(Event::Tick, TestPriority::Normal);
}
}
pub fn model(&self) -> &M {
&self.model
}
pub fn processed_count(&self) -> usize {
self.processed_events.len()
}
pub fn queue_len(&self) -> usize {
self.priority_queue.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Cmd;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct TestModel {
messages: Arc<Mutex<Vec<String>>>,
counter: Arc<AtomicUsize>,
}
impl Model for TestModel {
type Message = String;
fn update(&mut self, event: Event<Self::Message>) -> Cmd<Self::Message> {
if let Some(msg) = event.into_user() {
self.messages.lock().unwrap().push(msg);
self.counter.fetch_add(1, Ordering::SeqCst);
if self.counter.load(Ordering::SeqCst) >= 5 {
crate::commands::quit() } else {
Cmd::noop() }
} else {
Cmd::noop()
}
}
fn view(&self) -> String {
format!("Counter: {}", self.counter.load(Ordering::SeqCst))
}
}
#[test]
fn test_event_harness_basic() {
let model = TestModel {
messages: Arc::new(Mutex::new(Vec::new())),
counter: Arc::new(AtomicUsize::new(0)),
};
let messages = Arc::clone(&model.messages);
let mut harness = EventTestHarness::new(model);
for i in 0..5 {
harness.send_event(Event::User(format!("msg_{}", i)));
}
let processed = harness.process_all();
assert_eq!(processed.len(), 5);
assert!(harness.has_quit());
let msgs = messages.lock().unwrap();
assert_eq!(msgs.len(), 5);
}
#[test]
fn test_priority_harness() {
let model = TestModel {
messages: Arc::new(Mutex::new(Vec::new())),
counter: Arc::new(AtomicUsize::new(0)),
};
let mut harness = PriorityEventTestHarness::new(model);
harness.send_with_priority(Event::User("low".into()), TestPriority::Low);
harness.send_with_priority(Event::User("high".into()), TestPriority::High);
harness.send_with_priority(Event::User("normal".into()), TestPriority::Normal);
let mut processed_priorities = Vec::new();
while let Some((_, priority)) = harness.process_one() {
processed_priorities.push(priority as u8);
}
assert_eq!(processed_priorities, vec![1, 2, 3]);
assert_eq!(harness.processed_count(), 3);
}
}