use std::time::Duration;
#[derive(Debug, Clone)]
pub enum MockEvent {
Output(Vec<u8>),
Input(Vec<u8>),
Delay(Duration),
Exit(i32),
Eof,
Error(String),
Resize {
rows: u16,
cols: u16,
},
}
impl MockEvent {
pub fn output(data: impl Into<Vec<u8>>) -> Self {
Self::Output(data.into())
}
#[must_use]
pub fn output_str(s: &str) -> Self {
Self::Output(s.as_bytes().to_vec())
}
pub fn input(data: impl Into<Vec<u8>>) -> Self {
Self::Input(data.into())
}
#[must_use]
pub fn input_str(s: &str) -> Self {
Self::Input(s.as_bytes().to_vec())
}
#[must_use]
pub const fn delay(duration: Duration) -> Self {
Self::Delay(duration)
}
#[must_use]
pub const fn delay_ms(ms: u64) -> Self {
Self::Delay(Duration::from_millis(ms))
}
#[must_use]
pub const fn exit(code: i32) -> Self {
Self::Exit(code)
}
#[must_use]
pub const fn eof() -> Self {
Self::Eof
}
pub fn error(msg: impl Into<String>) -> Self {
Self::Error(msg.into())
}
#[must_use]
pub const fn resize(rows: u16, cols: u16) -> Self {
Self::Resize { rows, cols }
}
#[must_use]
pub const fn is_output(&self) -> bool {
matches!(self, Self::Output(_))
}
#[must_use]
pub const fn is_input(&self) -> bool {
matches!(self, Self::Input(_))
}
#[must_use]
pub const fn is_delay(&self) -> bool {
matches!(self, Self::Delay(_))
}
#[must_use]
pub const fn is_exit(&self) -> bool {
matches!(self, Self::Exit(_))
}
#[must_use]
pub const fn is_eof(&self) -> bool {
matches!(self, Self::Eof)
}
}
#[derive(Debug, Clone, Default)]
pub struct EventTimeline {
events: Vec<MockEvent>,
position: usize,
}
impl EventTimeline {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn from_events(events: Vec<MockEvent>) -> Self {
Self {
events,
position: 0,
}
}
pub fn push(&mut self, event: MockEvent) {
self.events.push(event);
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<&MockEvent> {
if self.position < self.events.len() {
let event = &self.events[self.position];
self.position += 1;
Some(event)
} else {
None
}
}
#[must_use]
pub fn peek(&self) -> Option<&MockEvent> {
self.events.get(self.position)
}
pub const fn reset(&mut self) {
self.position = 0;
}
#[must_use]
pub const fn has_more(&self) -> bool {
self.position < self.events.len()
}
#[must_use]
pub const fn remaining(&self) -> usize {
self.events.len().saturating_sub(self.position)
}
#[must_use]
pub fn events(&self) -> &[MockEvent] {
&self.events
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timeline_basic() {
let mut timeline = EventTimeline::new();
timeline.push(MockEvent::output_str("hello"));
timeline.push(MockEvent::delay_ms(100));
timeline.push(MockEvent::exit(0));
assert!(timeline.has_more());
assert!(timeline.next().unwrap().is_output());
assert!(timeline.next().unwrap().is_delay());
assert!(timeline.next().unwrap().is_exit());
assert!(!timeline.has_more());
}
#[test]
fn timeline_reset() {
let mut timeline =
EventTimeline::from_events(vec![MockEvent::output_str("test"), MockEvent::eof()]);
assert_eq!(timeline.remaining(), 2);
timeline.next();
assert_eq!(timeline.remaining(), 1);
timeline.reset();
assert_eq!(timeline.remaining(), 2);
}
}