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