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