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