use std::path::PathBuf;
use crossterm::event::KeyEvent;
use optionstratlib::ExpirationDate;
use crate::chain::MarketUpdate;
use crate::replay::LoadedBundle;
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum AppEvent {
Key(KeyEvent),
Resize(u16, u16),
Tick,
Market(MarketUpdate),
ReplaySeek(SeekTo),
ReplayControl(ReplayControl),
BundleLoaded(BundleLoadResult),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ReplayControl {
PlayPause,
SpeedFaster,
SpeedSlower,
}
#[derive(Debug, Clone)]
pub enum BundleLoadResult {
Loaded(Box<LoadedBundle>),
Failed(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeekTo {
Step(u32),
StepBy(i32),
}
#[derive(Debug, Clone)]
pub enum Command {
Subscribe {
underlying: String,
expiration: ExpirationDate,
},
Unsubscribe {
underlying: String,
expiration: ExpirationDate,
},
Reconnect,
Rediscover,
ReloadBundle(PathBuf),
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use optionstratlib::prelude::Positive;
use super::{AppEvent, BundleLoadResult, Command, ReplayControl, SeekTo};
use crate::chain::{MarketUpdate, ProviderId, StreamHealth};
#[track_caller]
fn pid(id: &str) -> ProviderId {
match ProviderId::new(id) {
Ok(p) => p,
Err(e) => panic!("expected a valid provider id `{id}`, got: {e}"),
}
}
#[track_caller]
fn pos(value: f64) -> Positive {
match Positive::new(value) {
Ok(p) => p,
Err(e) => panic!("invalid test positive `{value}`: {e}"),
}
}
#[test]
fn test_app_event_market_wraps_health_update() {
let event = AppEvent::Market(MarketUpdate::Health(pid("deribit"), StreamHealth::Live));
match event {
AppEvent::Market(MarketUpdate::Health(provider, StreamHealth::Live)) => {
assert_eq!(provider.as_str(), "deribit");
}
other => panic!("expected Market(Health(_, Live)), got {other:?}"),
}
}
#[test]
fn test_app_event_key_wraps_key_event() {
let event = AppEvent::Key(KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE));
match event {
AppEvent::Key(key) => assert_eq!(key.code, KeyCode::Char('q')),
other => panic!("expected Key, got {other:?}"),
}
}
#[test]
fn test_app_event_resize_carries_columns_and_rows() {
let event = AppEvent::Resize(120, 40);
match event {
AppEvent::Resize(cols, rows) => {
assert_eq!(cols, 120);
assert_eq!(rows, 40);
}
other => panic!("expected Resize, got {other:?}"),
}
}
#[test]
fn test_seek_to_step_and_step_by_are_distinct() {
assert_ne!(SeekTo::Step(0), SeekTo::StepBy(0));
assert_eq!(SeekTo::Step(3), SeekTo::Step(3));
let seek = SeekTo::StepBy(-1);
let _first = seek;
let _second = seek;
}
#[test]
fn test_command_reconnect_and_rediscover_construct() {
match Command::Reconnect {
Command::Reconnect => {}
other => panic!("expected Reconnect, got {other:?}"),
}
match Command::Rediscover {
Command::Rediscover => {}
other => panic!("expected Rediscover, got {other:?}"),
}
}
#[test]
fn test_command_subscribe_carries_underlying_and_expiration() {
let command = Command::Subscribe {
underlying: "BTC".to_owned(),
expiration: optionstratlib::ExpirationDate::Days(pos(30.0)),
};
match command {
Command::Subscribe {
underlying,
expiration,
} => {
assert_eq!(underlying, "BTC");
match expiration {
optionstratlib::ExpirationDate::Days(days) => assert_eq!(days, pos(30.0)),
other => panic!("expected Days, got {other:?}"),
}
}
other => panic!("expected Subscribe, got {other:?}"),
}
}
#[test]
fn test_command_reload_bundle_constructs() {
match Command::ReloadBundle(PathBuf::from("/bundle")) {
Command::ReloadBundle(dir) => assert_eq!(dir, PathBuf::from("/bundle")),
other => panic!("expected ReloadBundle, got {other:?}"),
}
}
#[test]
fn test_app_event_replay_control_and_bundle_loaded_construct() {
match AppEvent::ReplayControl(ReplayControl::PlayPause) {
AppEvent::ReplayControl(ReplayControl::PlayPause) => {}
other => panic!("expected ReplayControl(PlayPause), got {other:?}"),
}
match AppEvent::BundleLoaded(BundleLoadResult::Failed("bad bundle".to_owned())) {
AppEvent::BundleLoaded(BundleLoadResult::Failed(message)) => {
assert_eq!(message, "bad bundle");
}
other => panic!("expected BundleLoaded(Failed), got {other:?}"),
}
}
#[test]
fn test_app_event_match_is_wildcard_free() {
fn label(event: &AppEvent) -> &'static str {
match event {
AppEvent::Key(_) => "key",
AppEvent::Resize(_, _) => "resize",
AppEvent::Tick => "tick",
AppEvent::Market(_) => "market",
AppEvent::ReplaySeek(_) => "seek",
AppEvent::ReplayControl(_) => "control",
AppEvent::BundleLoaded(_) => "loaded",
}
}
assert_eq!(label(&AppEvent::Tick), "tick");
}
#[test]
fn test_replay_control_match_is_wildcard_free() {
fn label(control: ReplayControl) -> &'static str {
match control {
ReplayControl::PlayPause => "playpause",
ReplayControl::SpeedFaster => "faster",
ReplayControl::SpeedSlower => "slower",
}
}
assert_eq!(label(ReplayControl::PlayPause), "playpause");
assert_eq!(label(ReplayControl::SpeedFaster), "faster");
assert_eq!(label(ReplayControl::SpeedSlower), "slower");
}
}