use async_channel::{Receiver, Sender};
use crate::core::effect::Effect;
use crate::core::msg::Msg;
use crate::core::state::State;
use crate::core::update::update;
#[derive(Clone)]
pub struct Mailbox {
tx: Sender<Msg>,
}
impl Mailbox {
pub fn channel() -> (Mailbox, Receiver<Msg>) {
let (tx, rx) = async_channel::unbounded();
(Mailbox { tx }, rx)
}
pub fn send(&self, msg: Msg) {
let _ = self.tx.try_send(msg);
}
}
pub trait EffectRunner {
fn run(&mut self, effect: Effect, state: &State, mailbox: &Mailbox);
}
pub fn dispatch<R: EffectRunner>(state: &mut State, runner: &mut R, mailbox: &Mailbox, msg: Msg) {
let effects = update(state, msg);
for effect in effects {
runner.run(effect, state, mailbox);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::command::{Command, ScrollDir};
use crate::core::effect::Effect;
use crate::core::msg::{JsPurpose, Msg};
use crate::core::state::{Config, State};
#[derive(Default)]
struct TestRunner {
seen: Vec<Effect>,
scroll_answer: Option<String>,
}
impl EffectRunner for TestRunner {
fn run(&mut self, effect: Effect, _state: &State, mailbox: &Mailbox) {
if let Effect::EvalJs {
id,
tab,
purpose: JsPurpose::ReadScrollPercent,
..
} = &effect
&& let Some(answer) = self.scroll_answer.clone()
{
mailbox.send(Msg::JsResult {
id: *id,
tab: *tab,
result: Ok(answer),
});
}
self.seen.push(effect);
}
}
fn fixture() -> (State, TestRunner) {
let mut state = State::new(Config::default());
state.tabs.open("https://example.com");
state.tabs.focus_last();
(
state,
TestRunner {
scroll_answer: Some("55".to_string()),
..Default::default()
},
)
}
fn drain(state: &mut State, runner: &mut TestRunner, mailbox: &Mailbox, rx: &Receiver<Msg>) {
while let Ok(msg) = rx.try_recv() {
dispatch(state, runner, mailbox, msg);
}
}
#[test]
fn dispatch_processes_enqueued_command() {
let (mut state, mut runner) = fixture();
let (mailbox, rx) = Mailbox::channel();
mailbox.send(Msg::Command(Command::Quit));
drain(&mut state, &mut runner, &mailbox, &rx);
assert!(!state.running);
assert!(runner.seen.contains(&Effect::Quit));
}
#[test]
fn effect_enqueued_result_is_processed_in_same_drain() {
let (mut state, mut runner) = fixture();
let (mailbox, rx) = Mailbox::channel();
mailbox.send(Msg::Command(Command::Scroll(ScrollDir::Down, 1)));
drain(&mut state, &mut runner, &mailbox, &rx);
assert_eq!(state.status.scroll_percent, Some(55));
}
}