1use crate::helpers::Clock;
21use crate::{App, Cmd};
22use std::cell::Cell;
23use std::future::Future;
24use std::pin::Pin;
25use std::rc::Rc;
26use std::time::Duration;
27
28type BoxFuture<Msg> = Pin<Box<dyn Future<Output = Msg> + Send + 'static>>;
30
31#[derive(Clone)]
59pub struct FakeClock {
60 current: Rc<Cell<Duration>>,
61}
62
63impl FakeClock {
64 pub fn new() -> Self {
66 Self {
67 current: Rc::new(Cell::new(Duration::ZERO)),
68 }
69 }
70
71 pub fn advance(&self, duration: Duration) {
73 self.current.set(self.current.get() + duration);
74 }
75
76 pub fn set(&self, time: Duration) {
78 self.current.set(time);
79 }
80
81 pub fn get(&self) -> Duration {
83 self.current.get()
84 }
85
86 pub fn reset(&self) {
88 self.current.set(Duration::ZERO);
89 }
90}
91
92impl Default for FakeClock {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl Clock for FakeClock {
99 fn now(&self) -> Duration {
100 self.current.get()
101 }
102}
103
104pub struct TestRunner<A: App> {
108 model: A::Model,
109 commands: Vec<CmdRecord<A::Msg>>,
110 pending_tasks: Vec<BoxFuture<A::Msg>>,
111}
112
113#[derive(Debug)]
115pub enum CmdRecord<Msg> {
116 None,
117 Task,
118 Msg(Msg),
119 Batch(usize),
120}
121
122impl<A: App> TestRunner<A> {
123 pub fn new() -> Self {
125 let (model, init_cmd) = A::init();
126 let mut runner = Self {
127 model,
128 commands: Vec::new(),
129 pending_tasks: Vec::new(),
130 };
131 runner.record_cmd(init_cmd);
132 runner
133 }
134
135 pub fn with_model(model: A::Model) -> Self {
137 Self {
138 model,
139 commands: Vec::new(),
140 pending_tasks: Vec::new(),
141 }
142 }
143
144 pub fn send(&mut self, msg: A::Msg) -> &mut Self {
146 let cmd = A::update(&mut self.model, msg);
147 self.record_cmd(cmd);
148 self
149 }
150
151 pub fn send_all(&mut self, msgs: impl IntoIterator<Item = A::Msg>) -> &mut Self {
153 for msg in msgs {
154 self.send(msg);
155 }
156 self
157 }
158
159 pub fn model(&self) -> &A::Model {
161 &self.model
162 }
163
164 pub fn model_mut(&mut self) -> &mut A::Model {
166 &mut self.model
167 }
168
169 pub fn last_cmd(&self) -> Option<&CmdRecord<A::Msg>> {
171 self.commands.last()
172 }
173
174 pub fn commands(&self) -> &[CmdRecord<A::Msg>] {
176 &self.commands
177 }
178
179 pub fn clear_commands(&mut self) -> &mut Self {
181 self.commands.clear();
182 self
183 }
184
185 pub fn last_was_none(&self) -> bool {
187 matches!(self.last_cmd(), Some(CmdRecord::None))
188 }
189
190 pub fn last_was_task(&self) -> bool {
192 matches!(self.last_cmd(), Some(CmdRecord::Task))
193 }
194
195 pub fn last_was_msg(&self) -> bool {
197 matches!(self.last_cmd(), Some(CmdRecord::Msg(_)))
198 }
199
200 fn last_cmd_kind(&self) -> &'static str {
202 match self.last_cmd() {
203 Some(CmdRecord::None) => "None",
204 Some(CmdRecord::Task) => "Task",
205 Some(CmdRecord::Msg(_)) => "Msg",
206 Some(CmdRecord::Batch(_)) => "Batch",
207 None => "<no command>",
208 }
209 }
210
211 fn record_cmd(&mut self, cmd: Cmd<A::Msg>) {
212 let record = match cmd {
213 Cmd::None => CmdRecord::None,
214 Cmd::Task(future) => {
215 self.pending_tasks.push(future);
216 CmdRecord::Task
217 }
218 Cmd::Msg(msg) => CmdRecord::Msg(msg),
219 Cmd::Batch(cmds) => {
220 let len = cmds.len();
221 for cmd in cmds {
223 self.extract_tasks(cmd);
224 }
225 CmdRecord::Batch(len)
226 }
227 };
228 self.commands.push(record);
229 }
230
231 fn extract_tasks(&mut self, cmd: Cmd<A::Msg>) {
233 match cmd {
234 Cmd::None | Cmd::Msg(_) => {}
235 Cmd::Task(future) => {
236 self.pending_tasks.push(future);
237 }
238 Cmd::Batch(cmds) => {
239 for cmd in cmds {
240 self.extract_tasks(cmd);
241 }
242 }
243 }
244 }
245
246 pub fn pending_task_count(&self) -> usize {
252 self.pending_tasks.len()
253 }
254
255 pub fn has_pending_tasks(&self) -> bool {
257 !self.pending_tasks.is_empty()
258 }
259
260 pub async fn process_task(&mut self) -> bool {
276 if let Some(task) = self.pending_tasks.pop() {
277 let msg = task.await;
278 self.send(msg);
279 true
280 } else {
281 false
282 }
283 }
284
285 pub async fn process_tasks(&mut self) -> &mut Self {
298 while let Some(task) = self.pending_tasks.pop() {
299 let msg = task.await;
300 self.send(msg);
301 }
302 self
303 }
304
305 pub async fn process_n_tasks(&mut self, n: usize) -> &mut Self {
313 for i in 0..n {
314 assert!(
315 !self.pending_tasks.is_empty(),
316 "process_n_tasks: expected {} tasks but only {} were available",
317 n,
318 i
319 );
320 let task = self.pending_tasks.remove(0);
321 let msg = task.await;
322 self.send(msg);
323 }
324 self
325 }
326
327 pub fn expect_model(&mut self, predicate: impl FnOnce(&A::Model) -> bool) -> &mut Self {
345 assert!(
346 predicate(&self.model),
347 "expect_model: predicate returned false"
348 );
349 self
350 }
351
352 pub fn expect_model_msg(
357 &mut self,
358 predicate: impl FnOnce(&A::Model) -> bool,
359 msg: &str,
360 ) -> &mut Self {
361 assert!(predicate(&self.model), "expect_model: {}", msg);
362 self
363 }
364
365 pub fn expect_cmd_none(&mut self) -> &mut Self {
377 assert!(
378 self.last_was_none(),
379 "expect_cmd_none: last command was {}, expected None",
380 self.last_cmd_kind()
381 );
382 self
383 }
384
385 pub fn expect_cmd_task(&mut self) -> &mut Self {
397 assert!(
398 self.last_was_task(),
399 "expect_cmd_task: last command was {}, expected Task",
400 self.last_cmd_kind()
401 );
402 self
403 }
404
405 pub fn expect_cmd_msg(&mut self) -> &mut Self {
417 assert!(
418 self.last_was_msg(),
419 "expect_cmd_msg: last command was {}, expected Msg",
420 self.last_cmd_kind()
421 );
422 self
423 }
424
425 pub fn expect_cmd_msg_eq(&mut self, expected: A::Msg) -> &mut Self
437 where
438 A::Msg: PartialEq + std::fmt::Debug,
439 {
440 match self.last_cmd() {
441 Some(CmdRecord::Msg(msg)) => {
442 assert_eq!(msg, &expected, "expect_cmd_msg_eq: message mismatch");
443 }
444 _ => {
445 panic!(
446 "expect_cmd_msg_eq: last command was {}, expected Msg({:?})",
447 self.last_cmd_kind(),
448 expected
449 );
450 }
451 }
452 self
453 }
454
455 pub fn expect_cmd_batch(&mut self) -> &mut Self {
467 assert!(
468 matches!(self.last_cmd(), Some(CmdRecord::Batch(_))),
469 "expect_cmd_batch: last command was {}, expected Batch",
470 self.last_cmd_kind()
471 );
472 self
473 }
474
475 pub fn expect_cmd_batch_size(&mut self, expected_size: usize) -> &mut Self {
487 match self.last_cmd() {
488 Some(CmdRecord::Batch(size)) => {
489 assert_eq!(
490 *size, expected_size,
491 "expect_cmd_batch_size: batch size mismatch (got {}, expected {})",
492 size, expected_size
493 );
494 }
495 _ => {
496 panic!(
497 "expect_cmd_batch_size: last command was {}, expected Batch({})",
498 self.last_cmd_kind(),
499 expected_size
500 );
501 }
502 }
503 self
504 }
505}
506
507impl<A: App> Default for TestRunner<A> {
508 fn default() -> Self {
509 Self::new()
510 }
511}
512
513pub trait ModelAssert<T> {
515 fn assert_that(&self, predicate: impl FnOnce(&T) -> bool, msg: &str);
517}
518
519impl<A: App> ModelAssert<A::Model> for TestRunner<A> {
520 fn assert_that(&self, predicate: impl FnOnce(&A::Model) -> bool, msg: &str) {
521 assert!(predicate(&self.model), "{}", msg);
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 struct TestApp;
531
532 #[derive(Default)]
533 struct TestModel {
534 value: i32,
535 }
536
537 #[derive(Clone, Debug, PartialEq)]
538 enum TestMsg {
539 Inc,
540 Dec,
541 Set(i32),
542 Delayed,
543 MultiBatch,
544 AsyncFetch,
545 FetchResult(i32),
546 }
547
548 impl App for TestApp {
549 type Model = TestModel;
550 type Msg = TestMsg;
551
552 fn init() -> (Self::Model, Cmd<Self::Msg>) {
553 (TestModel::default(), Cmd::none())
554 }
555
556 fn update(model: &mut Self::Model, msg: Self::Msg) -> Cmd<Self::Msg> {
557 match msg {
558 TestMsg::Inc => model.value += 1,
559 TestMsg::Dec => model.value -= 1,
560 TestMsg::Set(v) => model.value = v,
561 TestMsg::Delayed => {
562 return Cmd::msg(TestMsg::Inc);
563 }
564 TestMsg::MultiBatch => {
565 return Cmd::batch([Cmd::msg(TestMsg::Inc), Cmd::msg(TestMsg::Inc)]);
566 }
567 TestMsg::AsyncFetch => {
568 return Cmd::task(async { TestMsg::FetchResult(42) });
569 }
570 TestMsg::FetchResult(v) => model.value = v,
571 }
572 Cmd::none()
573 }
574
575 fn view(_model: &Self::Model, _ctx: &mut crate::ViewCtx<Self::Msg>) {
576 }
578 }
579
580 #[test]
581 fn test_runner_basic() {
582 let mut runner = TestRunner::<TestApp>::new();
583
584 runner.send(TestMsg::Inc);
585 assert_eq!(runner.model().value, 1);
586
587 runner.send(TestMsg::Inc).send(TestMsg::Inc);
588 assert_eq!(runner.model().value, 3);
589
590 runner.send(TestMsg::Dec);
591 assert_eq!(runner.model().value, 2);
592 }
593
594 #[test]
595 fn test_runner_cmd_tracking() {
596 let mut runner = TestRunner::<TestApp>::new();
597
598 runner.send(TestMsg::Inc);
599 assert!(runner.last_was_none());
600
601 runner.send(TestMsg::Delayed);
602 assert!(runner.last_was_msg());
603 }
604
605 #[test]
606 fn test_runner_send_all() {
607 let mut runner = TestRunner::<TestApp>::new();
608
609 runner.send_all([TestMsg::Inc, TestMsg::Inc, TestMsg::Inc]);
610 assert_eq!(runner.model().value, 3);
611 }
612
613 #[test]
614 fn test_expect_model() {
615 let mut runner = TestRunner::<TestApp>::new();
616
617 runner
618 .send(TestMsg::Inc)
619 .expect_model(|m| m.value == 1)
620 .send(TestMsg::Inc)
621 .expect_model(|m| m.value == 2)
622 .send(TestMsg::Set(100))
623 .expect_model(|m| m.value == 100);
624 }
625
626 #[test]
627 fn test_expect_cmd_none() {
628 let mut runner = TestRunner::<TestApp>::new();
629
630 runner.send(TestMsg::Inc).expect_cmd_none();
631 }
632
633 #[test]
634 fn test_expect_cmd_msg() {
635 let mut runner = TestRunner::<TestApp>::new();
636
637 runner.send(TestMsg::Delayed).expect_cmd_msg();
638 }
639
640 #[test]
641 fn test_expect_cmd_msg_eq() {
642 let mut runner = TestRunner::<TestApp>::new();
643
644 runner
645 .send(TestMsg::Delayed)
646 .expect_cmd_msg_eq(TestMsg::Inc);
647 }
648
649 #[test]
650 fn test_expect_cmd_batch() {
651 let mut runner = TestRunner::<TestApp>::new();
652
653 runner
654 .send(TestMsg::MultiBatch)
655 .expect_cmd_batch()
656 .expect_cmd_batch_size(2);
657 }
658
659 #[test]
660 fn test_expect_chaining() {
661 let mut runner = TestRunner::<TestApp>::new();
663
664 runner
665 .send(TestMsg::Inc)
666 .expect_model(|m| m.value == 1)
667 .expect_cmd_none()
668 .send(TestMsg::Inc)
669 .expect_model(|m| m.value == 2)
670 .expect_cmd_none()
671 .send(TestMsg::Delayed)
672 .expect_model(|m| m.value == 2) .expect_cmd_msg_eq(TestMsg::Inc);
674 }
675
676 #[cfg(feature = "tokio")]
677 fn block_on<F: std::future::Future>(f: F) -> F::Output {
678 tokio::runtime::Builder::new_current_thread()
679 .enable_all()
680 .build()
681 .unwrap()
682 .block_on(f)
683 }
684
685 #[test]
686 #[cfg(feature = "tokio")]
687 fn test_process_task() {
688 block_on(async {
689 let mut runner = TestRunner::<TestApp>::new();
690
691 runner.send(TestMsg::AsyncFetch);
693 assert!(runner.last_was_task());
694 assert!(runner.has_pending_tasks());
695 assert_eq!(runner.pending_task_count(), 1);
696
697 assert_eq!(runner.model().value, 0);
699
700 runner.process_task().await;
702
703 assert!(!runner.has_pending_tasks());
705 assert_eq!(runner.model().value, 42);
706 });
707 }
708
709 #[test]
710 #[cfg(feature = "tokio")]
711 fn test_process_tasks() {
712 block_on(async {
713 let mut runner = TestRunner::<TestApp>::new();
714
715 runner.send(TestMsg::AsyncFetch);
717 runner.send(TestMsg::AsyncFetch);
718 assert_eq!(runner.pending_task_count(), 2);
719
720 runner.process_tasks().await;
722
723 assert!(!runner.has_pending_tasks());
725 assert_eq!(runner.model().value, 42);
727 });
728 }
729
730 #[test]
731 #[cfg(feature = "tokio")]
732 fn test_async_expect_chaining() {
733 block_on(async {
734 let mut runner = TestRunner::<TestApp>::new();
735
736 runner
737 .send(TestMsg::Inc)
738 .expect_model(|m| m.value == 1)
739 .expect_cmd_none()
740 .send(TestMsg::AsyncFetch)
741 .expect_cmd_task();
742
743 runner.process_tasks().await;
745
746 runner.expect_model(|m| m.value == 42);
747 });
748 }
749
750 #[test]
755 fn test_fake_clock_basic() {
756 let clock = super::FakeClock::new();
757
758 assert_eq!(clock.get(), Duration::ZERO);
759
760 clock.advance(Duration::from_millis(100));
761 assert_eq!(clock.get(), Duration::from_millis(100));
762
763 clock.advance(Duration::from_millis(50));
764 assert_eq!(clock.get(), Duration::from_millis(150));
765 }
766
767 #[test]
768 fn test_fake_clock_set_and_reset() {
769 let clock = super::FakeClock::new();
770
771 clock.set(Duration::from_secs(10));
772 assert_eq!(clock.get(), Duration::from_secs(10));
773
774 clock.reset();
775 assert_eq!(clock.get(), Duration::ZERO);
776 }
777
778 #[test]
779 fn test_fake_clock_shared() {
780 let clock1 = super::FakeClock::new();
781 let clock2 = clock1.clone();
782
783 clock1.advance(Duration::from_millis(100));
784
785 assert_eq!(clock2.get(), Duration::from_millis(100));
787 }
788
789 #[test]
790 #[cfg(feature = "tokio")]
791 fn test_debouncer_with_fake_clock() {
792 use crate::helpers::DebouncerWithClock;
793
794 let clock = super::FakeClock::new();
795 let mut debouncer = DebouncerWithClock::new(clock.clone());
796
797 let _cmd = debouncer.trigger(Duration::from_millis(500), ());
799 assert!(debouncer.is_pending());
800 assert!(!debouncer.should_fire()); clock.advance(Duration::from_millis(300));
804 assert!(!debouncer.should_fire());
805
806 clock.advance(Duration::from_millis(100));
808 assert!(!debouncer.should_fire());
809
810 clock.advance(Duration::from_millis(150));
812 assert!(debouncer.should_fire());
813 assert!(!debouncer.is_pending());
814 }
815
816 #[test]
817 #[cfg(feature = "tokio")]
818 fn test_debouncer_reset_with_fake_clock() {
819 use crate::helpers::DebouncerWithClock;
820
821 let clock = super::FakeClock::new();
822 let mut debouncer = DebouncerWithClock::new(clock.clone());
823
824 let _cmd = debouncer.trigger(Duration::from_millis(500), ());
826
827 clock.advance(Duration::from_millis(300));
829 assert!(!debouncer.should_fire());
830
831 let _cmd = debouncer.trigger(Duration::from_millis(500), ());
833
834 clock.advance(Duration::from_millis(300));
836 assert!(!debouncer.should_fire());
837
838 clock.advance(Duration::from_millis(250));
840 assert!(debouncer.should_fire());
841 }
842
843 #[test]
845 fn test_debouncer_with_fake_clock_mark_trigger() {
846 use crate::helpers::DebouncerWithClock;
847
848 let clock = super::FakeClock::new();
849 let mut debouncer = DebouncerWithClock::new(clock.clone());
850
851 debouncer.mark_trigger(Duration::from_millis(500));
853 assert!(debouncer.is_pending());
854 assert!(!debouncer.should_fire());
855
856 clock.advance(Duration::from_millis(550));
858 assert!(debouncer.should_fire());
859 assert!(!debouncer.is_pending());
860 }
861
862 #[test]
863 fn test_throttler_with_fake_clock() {
864 use crate::helpers::ThrottlerWithClock;
865
866 let clock = super::FakeClock::new();
867 let mut throttler = ThrottlerWithClock::new(clock.clone());
868 let interval = Duration::from_millis(100);
869
870 let cmd1 = throttler.run(interval, || Cmd::Msg(1));
872 assert!(cmd1.is_msg());
873
874 let cmd2 = throttler.run(interval, || Cmd::Msg(2));
876 assert!(cmd2.is_none());
877
878 clock.advance(Duration::from_millis(50));
880 let cmd3 = throttler.run(interval, || Cmd::Msg(3));
881 assert!(cmd3.is_none());
882
883 clock.advance(Duration::from_millis(60));
885 let cmd4 = throttler.run(interval, || Cmd::Msg(4));
886 assert!(cmd4.is_msg());
887 }
888
889 #[test]
890 fn test_throttler_time_remaining_with_fake_clock() {
891 use crate::helpers::ThrottlerWithClock;
892
893 let clock = super::FakeClock::new();
894 let mut throttler = ThrottlerWithClock::new(clock.clone());
895 let interval = Duration::from_millis(100);
896
897 assert!(throttler.time_remaining(interval).is_none());
899
900 let _ = throttler.run(interval, || Cmd::Msg(1));
902 let remaining = throttler.time_remaining(interval);
903 assert_eq!(remaining, Some(Duration::from_millis(100)));
904
905 clock.advance(Duration::from_millis(30));
907 let remaining = throttler.time_remaining(interval);
908 assert_eq!(remaining, Some(Duration::from_millis(70)));
909
910 clock.advance(Duration::from_millis(80));
912 assert!(throttler.time_remaining(interval).is_none());
913 }
914}