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