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