test_app_context.rs

  1use std::{
  2    cell::RefCell,
  3    marker::PhantomData,
  4    mem,
  5    path::PathBuf,
  6    rc::Rc,
  7    sync::{
  8        atomic::{AtomicUsize, Ordering},
  9        Arc,
 10    },
 11    time::Duration,
 12};
 13
 14use futures::Future;
 15use itertools::Itertools;
 16use parking_lot::{Mutex, RwLock};
 17use smol::stream::StreamExt;
 18
 19use crate::{
 20    executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
 21    AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, Handle, InputHandler,
 22    KeyDownEvent, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
 23    ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
 24    WeakHandle,
 25};
 26use collections::BTreeMap;
 27
 28use super::{
 29    ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
 30};
 31
 32pub struct TestAppContext {
 33    cx: Rc<RefCell<MutableAppContext>>,
 34    foreground_platform: Rc<platform::test::ForegroundPlatform>,
 35    condition_duration: Option<Duration>,
 36    pub function_name: String,
 37    assertion_context: AssertionContextManager,
 38}
 39
 40impl TestAppContext {
 41    pub fn new(
 42        foreground_platform: Rc<platform::test::ForegroundPlatform>,
 43        platform: Arc<dyn Platform>,
 44        foreground: Rc<executor::Foreground>,
 45        background: Arc<executor::Background>,
 46        font_cache: Arc<FontCache>,
 47        leak_detector: Arc<Mutex<LeakDetector>>,
 48        first_entity_id: usize,
 49        function_name: String,
 50    ) -> Self {
 51        let mut cx = MutableAppContext::new(
 52            foreground,
 53            background,
 54            platform,
 55            foreground_platform.clone(),
 56            font_cache,
 57            RefCounts::new(leak_detector),
 58            (),
 59        );
 60        cx.next_entity_id = first_entity_id;
 61        let cx = TestAppContext {
 62            cx: Rc::new(RefCell::new(cx)),
 63            foreground_platform,
 64            condition_duration: None,
 65            function_name,
 66            assertion_context: AssertionContextManager::new(),
 67        };
 68        cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
 69        cx
 70    }
 71
 72    pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
 73        let mut cx = self.cx.borrow_mut();
 74        if let Some(view_id) = cx.focused_view_id(window_id) {
 75            cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
 76        }
 77    }
 78
 79    pub fn dispatch_global_action<A: Action>(&self, action: A) {
 80        self.cx.borrow_mut().dispatch_global_action(action);
 81    }
 82
 83    pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
 84        let handled = self.cx.borrow_mut().update(|cx| {
 85            let presenter = cx
 86                .presenters_and_platform_windows
 87                .get(&window_id)
 88                .unwrap()
 89                .0
 90                .clone();
 91
 92            if cx.dispatch_keystroke(window_id, &keystroke) {
 93                return true;
 94            }
 95
 96            if presenter.borrow_mut().dispatch_event(
 97                Event::KeyDown(KeyDownEvent {
 98                    keystroke: keystroke.clone(),
 99                    is_held,
100                }),
101                false,
102                cx,
103            ) {
104                return true;
105            }
106
107            false
108        });
109
110        if !handled && !keystroke.cmd && !keystroke.ctrl {
111            WindowInputHandler {
112                app: self.cx.clone(),
113                window_id,
114            }
115            .replace_text_in_range(None, &keystroke.key)
116        }
117    }
118
119    pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
120    where
121        T: Entity,
122        F: FnOnce(&mut ModelContext<T>) -> T,
123    {
124        self.cx.borrow_mut().add_model(build_model)
125    }
126
127    pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
128    where
129        T: View,
130        F: FnOnce(&mut ViewContext<T>) -> T,
131    {
132        let (window_id, view) = self
133            .cx
134            .borrow_mut()
135            .add_window(Default::default(), build_root_view);
136        self.simulate_window_activation(Some(window_id));
137        (window_id, view)
138    }
139
140    pub fn add_view<T, F>(
141        &mut self,
142        parent_handle: impl Into<AnyViewHandle>,
143        build_view: F,
144    ) -> ViewHandle<T>
145    where
146        T: View,
147        F: FnOnce(&mut ViewContext<T>) -> T,
148    {
149        self.cx.borrow_mut().add_view(parent_handle, build_view)
150    }
151
152    pub fn window_ids(&self) -> Vec<usize> {
153        self.cx.borrow().window_ids().collect()
154    }
155
156    pub fn root_view<T: View>(&self, window_id: usize) -> Option<ViewHandle<T>> {
157        self.cx.borrow().root_view(window_id)
158    }
159
160    pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
161        callback(self.cx.borrow().as_ref())
162    }
163
164    pub fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
165        let mut state = self.cx.borrow_mut();
166        // Don't increment pending flushes in order for effects to be flushed before the callback
167        // completes, which is helpful in tests.
168        let result = callback(&mut *state);
169        // Flush effects after the callback just in case there are any. This can happen in edge
170        // cases such as the closure dropping handles.
171        state.flush_effects();
172        result
173    }
174
175    pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
176    where
177        F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
178        V: View,
179    {
180        handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
181            let mut render_cx = RenderContext {
182                app: cx,
183                window_id: handle.window_id(),
184                view_id: handle.id(),
185                view_type: PhantomData,
186                titlebar_height: 0.,
187                hovered_region_ids: Default::default(),
188                clicked_region_ids: None,
189                refreshing: false,
190                appearance: Appearance::Light,
191            };
192            f(view, &mut render_cx)
193        })
194    }
195
196    pub fn to_async(&self) -> AsyncAppContext {
197        AsyncAppContext(self.cx.clone())
198    }
199
200    pub fn font_cache(&self) -> Arc<FontCache> {
201        self.cx.borrow().cx.font_cache.clone()
202    }
203
204    pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
205        self.foreground_platform.clone()
206    }
207
208    pub fn platform(&self) -> Arc<dyn platform::Platform> {
209        self.cx.borrow().cx.platform.clone()
210    }
211
212    pub fn foreground(&self) -> Rc<executor::Foreground> {
213        self.cx.borrow().foreground().clone()
214    }
215
216    pub fn background(&self) -> Arc<executor::Background> {
217        self.cx.borrow().background().clone()
218    }
219
220    pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
221    where
222        F: FnOnce(AsyncAppContext) -> Fut,
223        Fut: 'static + Future<Output = T>,
224        T: 'static,
225    {
226        let foreground = self.foreground();
227        let future = f(self.to_async());
228        let cx = self.to_async();
229        foreground.spawn(async move {
230            let result = future.await;
231            cx.0.borrow_mut().flush_effects();
232            result
233        })
234    }
235
236    pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
237        self.foreground_platform.simulate_new_path_selection(result);
238    }
239
240    pub fn did_prompt_for_new_path(&self) -> bool {
241        self.foreground_platform.as_ref().did_prompt_for_new_path()
242    }
243
244    pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
245        use postage::prelude::Sink as _;
246
247        let mut done_tx = self
248            .window_mut(window_id)
249            .pending_prompts
250            .borrow_mut()
251            .pop_front()
252            .expect("prompt was not called");
253        let _ = done_tx.try_send(answer);
254    }
255
256    pub fn has_pending_prompt(&self, window_id: usize) -> bool {
257        let window = self.window_mut(window_id);
258        let prompts = window.pending_prompts.borrow_mut();
259        !prompts.is_empty()
260    }
261
262    pub fn current_window_title(&self, window_id: usize) -> Option<String> {
263        self.window_mut(window_id).title.clone()
264    }
265
266    pub fn simulate_window_close(&self, window_id: usize) -> bool {
267        let handler = self.window_mut(window_id).should_close_handler.take();
268        if let Some(mut handler) = handler {
269            let should_close = handler();
270            self.window_mut(window_id).should_close_handler = Some(handler);
271            should_close
272        } else {
273            false
274        }
275    }
276
277    pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
278        let mut window = self.window_mut(window_id);
279        window.size = size;
280        let mut handlers = mem::take(&mut window.resize_handlers);
281        drop(window);
282        for handler in &mut handlers {
283            handler();
284        }
285        self.window_mut(window_id).resize_handlers = handlers;
286    }
287
288    pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
289        let mut handlers = BTreeMap::new();
290        {
291            let mut cx = self.cx.borrow_mut();
292            for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows {
293                let window = window
294                    .as_any_mut()
295                    .downcast_mut::<platform::test::Window>()
296                    .unwrap();
297                handlers.insert(
298                    *window_id,
299                    mem::take(&mut window.active_status_change_handlers),
300                );
301            }
302        };
303        let mut handlers = handlers.into_iter().collect::<Vec<_>>();
304        handlers.sort_unstable_by_key(|(window_id, _)| Some(*window_id) == to_activate);
305
306        for (window_id, mut window_handlers) in handlers {
307            for window_handler in &mut window_handlers {
308                window_handler(Some(window_id) == to_activate);
309            }
310
311            self.window_mut(window_id)
312                .active_status_change_handlers
313                .extend(window_handlers);
314        }
315    }
316
317    pub fn is_window_edited(&self, window_id: usize) -> bool {
318        self.window_mut(window_id).edited
319    }
320
321    pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
322        self.cx.borrow().leak_detector()
323    }
324
325    pub fn assert_dropped(&self, handle: impl WeakHandle) {
326        self.cx
327            .borrow()
328            .leak_detector()
329            .lock()
330            .assert_dropped(handle.id())
331    }
332
333    /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
334    /// where the stray handles were created.
335    pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
336        let weak = handle.downgrade();
337        self.update(|_| drop(handle));
338        self.assert_dropped(weak);
339    }
340
341    fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
342        std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
343            let (_, window) = state
344                .presenters_and_platform_windows
345                .get_mut(&window_id)
346                .unwrap();
347            let test_window = window
348                .as_any_mut()
349                .downcast_mut::<platform::test::Window>()
350                .unwrap();
351            test_window
352        })
353    }
354
355    pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
356        self.condition_duration = duration;
357    }
358
359    pub fn condition_duration(&self) -> Duration {
360        self.condition_duration.unwrap_or_else(|| {
361            if std::env::var("CI").is_ok() {
362                Duration::from_secs(2)
363            } else {
364                Duration::from_millis(500)
365            }
366        })
367    }
368
369    pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
370        self.update(|cx| {
371            let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
372            let expected_content = expected_content.map(|content| content.to_owned());
373            assert_eq!(actual_content, expected_content);
374        })
375    }
376
377    pub fn add_assertion_context(&self, context: String) -> ContextHandle {
378        self.assertion_context.add_context(context)
379    }
380
381    pub fn assertion_context(&self) -> String {
382        self.assertion_context.context()
383    }
384}
385
386impl UpdateModel for TestAppContext {
387    fn update_model<T: Entity, O>(
388        &mut self,
389        handle: &ModelHandle<T>,
390        update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> O,
391    ) -> O {
392        self.cx.borrow_mut().update_model(handle, update)
393    }
394}
395
396impl ReadModelWith for TestAppContext {
397    fn read_model_with<E: Entity, T>(
398        &self,
399        handle: &ModelHandle<E>,
400        read: &mut dyn FnMut(&E, &AppContext) -> T,
401    ) -> T {
402        let cx = self.cx.borrow();
403        let cx = cx.as_ref();
404        read(handle.read(cx), cx)
405    }
406}
407
408impl UpdateView for TestAppContext {
409    fn update_view<T, S>(
410        &mut self,
411        handle: &ViewHandle<T>,
412        update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
413    ) -> S
414    where
415        T: View,
416    {
417        self.cx.borrow_mut().update_view(handle, update)
418    }
419}
420
421impl ReadViewWith for TestAppContext {
422    fn read_view_with<V, T>(
423        &self,
424        handle: &ViewHandle<V>,
425        read: &mut dyn FnMut(&V, &AppContext) -> T,
426    ) -> T
427    where
428        V: View,
429    {
430        let cx = self.cx.borrow();
431        let cx = cx.as_ref();
432        read(handle.read(cx), cx)
433    }
434}
435
436impl<T: Entity> ModelHandle<T> {
437    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
438        let (tx, mut rx) = futures::channel::mpsc::unbounded();
439        let mut cx = cx.cx.borrow_mut();
440        let subscription = cx.observe(self, move |_, _| {
441            tx.unbounded_send(()).ok();
442        });
443
444        let duration = if std::env::var("CI").is_ok() {
445            Duration::from_secs(5)
446        } else {
447            Duration::from_secs(1)
448        };
449
450        async move {
451            let notification = crate::util::timeout(duration, rx.next())
452                .await
453                .expect("next notification timed out");
454            drop(subscription);
455            notification.expect("model dropped while test was waiting for its next notification")
456        }
457    }
458
459    pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
460    where
461        T::Event: Clone,
462    {
463        let (tx, mut rx) = futures::channel::mpsc::unbounded();
464        let mut cx = cx.cx.borrow_mut();
465        let subscription = cx.subscribe(self, move |_, event, _| {
466            tx.unbounded_send(event.clone()).ok();
467        });
468
469        let duration = if std::env::var("CI").is_ok() {
470            Duration::from_secs(5)
471        } else {
472            Duration::from_secs(1)
473        };
474
475        cx.foreground.start_waiting();
476        async move {
477            let event = crate::util::timeout(duration, rx.next())
478                .await
479                .expect("next event timed out");
480            drop(subscription);
481            event.expect("model dropped while test was waiting for its next event")
482        }
483    }
484
485    pub fn condition(
486        &self,
487        cx: &TestAppContext,
488        mut predicate: impl FnMut(&T, &AppContext) -> bool,
489    ) -> impl Future<Output = ()> {
490        let (tx, mut rx) = futures::channel::mpsc::unbounded();
491
492        let mut cx = cx.cx.borrow_mut();
493        let subscriptions = (
494            cx.observe(self, {
495                let tx = tx.clone();
496                move |_, _| {
497                    tx.unbounded_send(()).ok();
498                }
499            }),
500            cx.subscribe(self, {
501                move |_, _, _| {
502                    tx.unbounded_send(()).ok();
503                }
504            }),
505        );
506
507        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
508        let handle = self.downgrade();
509        let duration = if std::env::var("CI").is_ok() {
510            Duration::from_secs(5)
511        } else {
512            Duration::from_secs(1)
513        };
514
515        async move {
516            crate::util::timeout(duration, async move {
517                loop {
518                    {
519                        let cx = cx.borrow();
520                        let cx = cx.as_ref();
521                        if predicate(
522                            handle
523                                .upgrade(cx)
524                                .expect("model dropped with pending condition")
525                                .read(cx),
526                            cx,
527                        ) {
528                            break;
529                        }
530                    }
531
532                    cx.borrow().foreground().start_waiting();
533                    rx.next()
534                        .await
535                        .expect("model dropped with pending condition");
536                    cx.borrow().foreground().finish_waiting();
537                }
538            })
539            .await
540            .expect("condition timed out");
541            drop(subscriptions);
542        }
543    }
544}
545
546impl<T: View> ViewHandle<T> {
547    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
548        use postage::prelude::{Sink as _, Stream as _};
549
550        let (mut tx, mut rx) = postage::mpsc::channel(1);
551        let mut cx = cx.cx.borrow_mut();
552        let subscription = cx.observe(self, move |_, _| {
553            tx.try_send(()).ok();
554        });
555
556        let duration = if std::env::var("CI").is_ok() {
557            Duration::from_secs(5)
558        } else {
559            Duration::from_secs(1)
560        };
561
562        async move {
563            let notification = crate::util::timeout(duration, rx.recv())
564                .await
565                .expect("next notification timed out");
566            drop(subscription);
567            notification.expect("model dropped while test was waiting for its next notification")
568        }
569    }
570
571    pub fn condition(
572        &self,
573        cx: &TestAppContext,
574        mut predicate: impl FnMut(&T, &AppContext) -> bool,
575    ) -> impl Future<Output = ()> {
576        use postage::prelude::{Sink as _, Stream as _};
577
578        let (tx, mut rx) = postage::mpsc::channel(1024);
579        let timeout_duration = cx.condition_duration();
580
581        let mut cx = cx.cx.borrow_mut();
582        let subscriptions = self.update(&mut *cx, |_, cx| {
583            (
584                cx.observe(self, {
585                    let mut tx = tx.clone();
586                    move |_, _, _| {
587                        tx.blocking_send(()).ok();
588                    }
589                }),
590                cx.subscribe(self, {
591                    let mut tx = tx.clone();
592                    move |_, _, _, _| {
593                        tx.blocking_send(()).ok();
594                    }
595                }),
596            )
597        });
598
599        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
600        let handle = self.downgrade();
601
602        async move {
603            crate::util::timeout(timeout_duration, async move {
604                loop {
605                    {
606                        let cx = cx.borrow();
607                        let cx = cx.as_ref();
608                        if predicate(
609                            handle
610                                .upgrade(cx)
611                                .expect("view dropped with pending condition")
612                                .read(cx),
613                            cx,
614                        ) {
615                            break;
616                        }
617                    }
618
619                    cx.borrow().foreground().start_waiting();
620                    rx.recv()
621                        .await
622                        .expect("view dropped with pending condition");
623                    cx.borrow().foreground().finish_waiting();
624                }
625            })
626            .await
627            .expect("condition timed out");
628            drop(subscriptions);
629        }
630    }
631}
632
633/// Tracks string context to be printed when assertions fail.
634/// Often this is done by storing a context string in the manager and returning the handle.
635#[derive(Clone)]
636pub struct AssertionContextManager {
637    id: Arc<AtomicUsize>,
638    contexts: Arc<RwLock<BTreeMap<usize, String>>>,
639}
640
641impl AssertionContextManager {
642    pub fn new() -> Self {
643        Self {
644            id: Arc::new(AtomicUsize::new(0)),
645            contexts: Arc::new(RwLock::new(BTreeMap::new())),
646        }
647    }
648
649    pub fn add_context(&self, context: String) -> ContextHandle {
650        let id = self.id.fetch_add(1, Ordering::Relaxed);
651        let mut contexts = self.contexts.write();
652        contexts.insert(id, context);
653        ContextHandle {
654            id,
655            manager: self.clone(),
656        }
657    }
658
659    pub fn context(&self) -> String {
660        let contexts = self.contexts.read();
661        format!("\n{}\n", contexts.values().join("\n"))
662    }
663}
664
665/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
666/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
667/// the state that was set initially for the failure can be printed in the error message
668pub struct ContextHandle {
669    id: usize,
670    manager: AssertionContextManager,
671}
672
673impl Drop for ContextHandle {
674    fn drop(&mut self) {
675        let mut contexts = self.manager.contexts.write();
676        contexts.remove(&self.id);
677    }
678}