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