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