use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt::Debug;
use std::rc::Rc;
use std::time::{Duration, Instant};
use futures::executor::{LocalPool, LocalSpawner};
use futures::stream::iter;
use futures::task::LocalSpawnExt;
use futures::{pin_mut, Stream, StreamExt};
pub use clock::TestClock;
use crate::dependencies::{guard::Guard, Dependency};
use crate::effects::{scheduler::Reactor, Delay, Effects, Scheduler};
use crate::reducer::Reducer;
use crate::Task;
mod clock;
#[doc = include_str!("README.md")]
pub struct TestStore<State: Reducer>
where
<State as Reducer>::Action: Debug,
{
state: Option<State>, pool: LocalPool,
inner: Rc<RefCell<Inner<<State as Reducer>::Action>>>,
reactor: Guard<Reactor>,
}
impl<State: Reducer> Default for TestStore<State>
where
State: Default,
<State as Reducer>::Action: Debug,
{
fn default() -> Self {
Self::new(|| State::default())
}
}
impl<State: Reducer> Drop for TestStore<State>
where
<State as Reducer>::Action: Debug,
{
#[track_caller]
fn drop(&mut self) {
assert!(
self.inner.borrow().actions.is_empty(),
"one or more extra actions were not tested for: {:#?}",
self.inner
.borrow_mut()
.actions
.drain(..)
.collect::<Vec<_>>()
);
}
}
impl<State: Reducer> TestClock for TestStore<State>
where
<State as Reducer>::Action: Debug,
{
fn advance(&mut self, duration: Duration) {
let mut inner = self.inner.borrow_mut();
let now = inner.now + duration;
inner.now = now;
drop(inner);
let timer = Dependency::<Reactor>::new();
loop {
self.pool.run_until_stalled();
timer.poll(now);
if self.pool.try_run_one() == false {
break;
}
}
}
}
impl<State: Reducer> TestStore<State>
where
<State as Reducer>::Action: Debug,
{
pub fn new<F>(with: F) -> Self
where
F: (FnOnce() -> State),
{
Self::with_initial(with())
}
pub fn with_initial(state: State) -> Self {
let pool = LocalPool::new();
let spawner = pool.spawner();
Self {
state: Some(state),
inner: Inner::new(spawner),
reactor: Guard::new(Reactor::new()),
pool,
}
}
#[track_caller]
pub fn send(&mut self, action: <State as Reducer>::Action, assert: impl FnOnce(&mut State))
where
State: Clone + Debug + PartialEq,
<State as Reducer>::Action: 'static,
{
let mut expected = self.state.clone();
assert(expected.as_mut().unwrap());
assert!(
self.inner.borrow().actions.is_empty(),
"an extra action was received: {:#?}",
self.inner
.borrow_mut()
.actions
.drain(..)
.collect::<Vec<_>>()
);
self.state
.as_mut()
.unwrap()
.reduce(action, self.inner.clone());
assert_eq!(self.state, expected);
}
#[track_caller]
pub fn recv(&mut self, action: <State as Reducer>::Action, assert: impl FnOnce(&mut State))
where
State: Clone + Debug + PartialEq,
<State as Reducer>::Action: Debug + PartialEq + 'static,
{
let mut expected = self.state.clone();
assert(expected.as_mut().unwrap());
let received = self
.inner
.borrow_mut()
.actions
.pop_front()
.expect("no action received");
assert_eq!(received, action);
self.state
.as_mut()
.unwrap()
.reduce(action, self.inner.clone());
assert_eq!(self.state, expected);
}
pub fn wait(&mut self) {
self.pool.run()
}
pub fn into_inner(mut self) -> <State as Reducer>::Output
where
State: Into<<State as Reducer>::Output>,
{
self.state.take().unwrap().into()
}
}
struct Inner<Action> {
actions: VecDeque<Action>,
spawner: LocalSpawner,
now: Instant,
}
#[doc(hidden)]
impl<Action> Effects for Rc<RefCell<Inner<Action>>>
where
Action: Debug + 'static,
{
type Action = Action;
fn action(&self, action: impl Into<<Self as Effects>::Action>) {
self.borrow_mut().actions.push_back(action.into());
}
fn task<S: Stream<Item = Action> + 'static>(&self, stream: S) -> Task {
let effects = self.clone();
let spawner = self.borrow().spawner.clone();
let handle = spawner
.spawn_local_with_handle(async move {
pin_mut!(stream);
while let Some(action) = stream.next().await {
effects.action(action);
}
})
.ok();
Task { handle, when: None }
}
}
#[doc(hidden)]
impl<Action> Scheduler for Rc<RefCell<Inner<Action>>>
where
Action: Debug + 'static,
{
type Action = Action;
fn now(&self) -> Instant {
self.borrow().now
}
fn schedule(
&self,
action: Self::Action,
delays: impl IntoIterator<Item = Delay> + 'static,
) -> Task
where
Self::Action: Clone + 'static,
{
self.task(iter(delays).then(move |delay| {
let action = action.clone();
async move {
delay.await;
action.clone()
}
}))
}
}
impl<Action> Inner<Action> {
fn new(spawner: LocalSpawner) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
actions: Default::default(),
now: Instant::now().into(),
spawner,
}))
}
}