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