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