1use crate::{
2 executor,
3 geometry::vector::Vector2F,
4 keymap_matcher::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 smol::stream::StreamExt;
16use std::{
17 any::Any,
18 cell::RefCell,
19 mem,
20 path::PathBuf,
21 rc::Rc,
22 sync::{
23 atomic::{AtomicUsize, Ordering},
24 Arc,
25 },
26 time::Duration,
27};
28
29use super::{
30 ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
31};
32
33#[derive(Clone)]
34pub struct TestAppContext {
35 cx: Rc<RefCell<AppContext>>,
36 foreground_platform: Rc<platform::test::ForegroundPlatform>,
37 condition_duration: Option<Duration>,
38 pub function_name: String,
39 assertion_context: AssertionContextManager,
40}
41
42impl TestAppContext {
43 pub fn new(
44 foreground_platform: Rc<platform::test::ForegroundPlatform>,
45 platform: Arc<dyn Platform>,
46 foreground: Rc<executor::Foreground>,
47 background: Arc<executor::Background>,
48 font_cache: Arc<FontCache>,
49 leak_detector: Arc<Mutex<LeakDetector>>,
50 first_entity_id: usize,
51 function_name: String,
52 ) -> Self {
53 let mut cx = AppContext::new(
54 foreground,
55 background,
56 platform,
57 foreground_platform.clone(),
58 font_cache,
59 RefCounts::new(leak_detector),
60 (),
61 );
62 cx.next_entity_id = first_entity_id;
63 let cx = TestAppContext {
64 cx: Rc::new(RefCell::new(cx)),
65 foreground_platform,
66 condition_duration: None,
67 function_name,
68 assertion_context: AssertionContextManager::new(),
69 };
70 cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
71 cx
72 }
73
74 pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
75 self.cx
76 .borrow_mut()
77 .update_window(window_id, |window| {
78 window.dispatch_action(window.focused_view_id(), &action);
79 })
80 .expect("window not found");
81 }
82
83 pub fn dispatch_global_action<A: Action>(&self, action: A) {
84 self.cx.borrow_mut().dispatch_global_action_any(&action);
85 }
86
87 pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
88 let handled = self
89 .cx
90 .borrow_mut()
91 .update_window(window_id, |cx| {
92 if cx.dispatch_keystroke(&keystroke) {
93 return true;
94 }
95
96 if cx.dispatch_event(
97 Event::KeyDown(KeyDownEvent {
98 keystroke: keystroke.clone(),
99 is_held,
100 }),
101 false,
102 ) {
103 return true;
104 }
105
106 false
107 })
108 .unwrap_or(false);
109
110 if !handled && !keystroke.cmd && !keystroke.ctrl {
111 WindowInputHandler {
112 app: self.cx.clone(),
113 window_id,
114 }
115 .replace_text_in_range(None, &keystroke.key)
116 }
117 }
118
119 pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
120 &self,
121 window_id: usize,
122 callback: F,
123 ) -> Option<T> {
124 self.cx.borrow().read_window(window_id, callback)
125 }
126
127 pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
128 &mut self,
129 window_id: usize,
130 callback: F,
131 ) -> Option<T> {
132 self.cx.borrow_mut().update_window(window_id, callback)
133 }
134
135 pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
136 where
137 T: Entity,
138 F: FnOnce(&mut ModelContext<T>) -> T,
139 {
140 self.cx.borrow_mut().add_model(build_model)
141 }
142
143 pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
144 where
145 T: View,
146 F: FnOnce(&mut ViewContext<T>) -> T,
147 {
148 let (window_id, view) = self
149 .cx
150 .borrow_mut()
151 .add_window(Default::default(), build_root_view);
152 self.simulate_window_activation(Some(window_id));
153 (window_id, view)
154 }
155
156 pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
157 where
158 T: View,
159 F: FnOnce(&mut ViewContext<T>) -> T,
160 {
161 self.update_window(window_id, |cx| cx.add_view(build_view))
162 .expect("window not found")
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 window_ids(&self) -> Vec<usize> {
186 self.cx.borrow().windows.keys().copied().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 simulate_prompt_answer(&self, window_id: usize, answer: usize) {
257 use postage::prelude::Sink as _;
258
259 let mut done_tx = self
260 .platform_window_mut(window_id)
261 .pending_prompts
262 .borrow_mut()
263 .pop_front()
264 .expect("prompt was not called");
265 let _ = done_tx.try_send(answer);
266 }
267
268 pub fn has_pending_prompt(&self, window_id: usize) -> bool {
269 let window = self.platform_window_mut(window_id);
270 let prompts = window.pending_prompts.borrow_mut();
271 !prompts.is_empty()
272 }
273
274 pub fn current_window_title(&self, window_id: usize) -> Option<String> {
275 self.platform_window_mut(window_id).title.clone()
276 }
277
278 pub fn simulate_window_close(&self, window_id: usize) -> bool {
279 let handler = self
280 .platform_window_mut(window_id)
281 .should_close_handler
282 .take();
283 if let Some(mut handler) = handler {
284 let should_close = handler();
285 self.platform_window_mut(window_id).should_close_handler = Some(handler);
286 should_close
287 } else {
288 false
289 }
290 }
291
292 pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
293 let mut window = self.platform_window_mut(window_id);
294 window.size = size;
295 let mut handlers = mem::take(&mut window.resize_handlers);
296 drop(window);
297 for handler in &mut handlers {
298 handler();
299 }
300 self.platform_window_mut(window_id).resize_handlers = handlers;
301 }
302
303 pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
304 self.cx.borrow_mut().update(|cx| {
305 let other_window_ids = cx
306 .windows
307 .keys()
308 .filter(|window_id| Some(**window_id) != to_activate)
309 .copied()
310 .collect::<Vec<_>>();
311
312 for window_id in other_window_ids {
313 cx.window_changed_active_status(window_id, false)
314 }
315
316 if let Some(to_activate) = to_activate {
317 cx.window_changed_active_status(to_activate, true)
318 }
319 });
320 }
321
322 pub fn is_window_edited(&self, window_id: usize) -> bool {
323 self.platform_window_mut(window_id).edited
324 }
325
326 pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
327 self.cx.borrow().leak_detector()
328 }
329
330 pub fn assert_dropped(&self, handle: impl WeakHandle) {
331 self.cx
332 .borrow()
333 .leak_detector()
334 .lock()
335 .assert_dropped(handle.id())
336 }
337
338 /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
339 /// where the stray handles were created.
340 pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
341 let weak = handle.downgrade();
342 self.update(|_| drop(handle));
343 self.assert_dropped(weak);
344 }
345
346 fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
347 std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
348 let window = state.windows.get_mut(&window_id).unwrap();
349 let test_window = window
350 .platform_window
351 .as_any_mut()
352 .downcast_mut::<platform::test::Window>()
353 .unwrap();
354 test_window
355 })
356 }
357
358 pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
359 self.condition_duration = duration;
360 }
361
362 pub fn condition_duration(&self) -> Duration {
363 self.condition_duration.unwrap_or_else(|| {
364 if std::env::var("CI").is_ok() {
365 Duration::from_secs(2)
366 } else {
367 Duration::from_millis(500)
368 }
369 })
370 }
371
372 pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
373 self.update(|cx| {
374 let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
375 let expected_content = expected_content.map(|content| content.to_owned());
376 assert_eq!(actual_content, expected_content);
377 })
378 }
379
380 pub fn add_assertion_context(&self, context: String) -> ContextHandle {
381 self.assertion_context.add_context(context)
382 }
383
384 pub fn assertion_context(&self) -> String {
385 self.assertion_context.context()
386 }
387}
388
389impl BorrowAppContext for TestAppContext {
390 fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
391 self.cx.borrow().read_with(f)
392 }
393
394 fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
395 self.cx.borrow_mut().update(f)
396 }
397}
398
399impl BorrowWindowContext for TestAppContext {
400 fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
401 self.cx
402 .borrow()
403 .read_window(window_id, f)
404 .expect("window was closed")
405 }
406
407 fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
408 self.cx
409 .borrow_mut()
410 .update_window(window_id, f)
411 .expect("window was closed")
412 }
413}
414
415impl<T: Entity> ModelHandle<T> {
416 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
417 let (tx, mut rx) = futures::channel::mpsc::unbounded();
418 let mut cx = cx.cx.borrow_mut();
419 let subscription = cx.observe(self, move |_, _| {
420 tx.unbounded_send(()).ok();
421 });
422
423 let duration = if std::env::var("CI").is_ok() {
424 Duration::from_secs(5)
425 } else {
426 Duration::from_secs(1)
427 };
428
429 async move {
430 let notification = crate::util::timeout(duration, rx.next())
431 .await
432 .expect("next notification timed out");
433 drop(subscription);
434 notification.expect("model dropped while test was waiting for its next notification")
435 }
436 }
437
438 pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
439 where
440 T::Event: Clone,
441 {
442 let (tx, mut rx) = futures::channel::mpsc::unbounded();
443 let mut cx = cx.cx.borrow_mut();
444 let subscription = cx.subscribe(self, move |_, event, _| {
445 tx.unbounded_send(event.clone()).ok();
446 });
447
448 let duration = if std::env::var("CI").is_ok() {
449 Duration::from_secs(5)
450 } else {
451 Duration::from_secs(1)
452 };
453
454 cx.foreground.start_waiting();
455 async move {
456 let event = crate::util::timeout(duration, rx.next())
457 .await
458 .expect("next event timed out");
459 drop(subscription);
460 event.expect("model dropped while test was waiting for its next event")
461 }
462 }
463
464 pub fn condition(
465 &self,
466 cx: &TestAppContext,
467 mut predicate: impl FnMut(&T, &AppContext) -> bool,
468 ) -> impl Future<Output = ()> {
469 let (tx, mut rx) = futures::channel::mpsc::unbounded();
470
471 let mut cx = cx.cx.borrow_mut();
472 let subscriptions = (
473 cx.observe(self, {
474 let tx = tx.clone();
475 move |_, _| {
476 tx.unbounded_send(()).ok();
477 }
478 }),
479 cx.subscribe(self, {
480 move |_, _, _| {
481 tx.unbounded_send(()).ok();
482 }
483 }),
484 );
485
486 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
487 let handle = self.downgrade();
488 let duration = if std::env::var("CI").is_ok() {
489 Duration::from_secs(5)
490 } else {
491 Duration::from_secs(1)
492 };
493
494 async move {
495 crate::util::timeout(duration, async move {
496 loop {
497 {
498 let cx = cx.borrow();
499 let cx = &*cx;
500 if predicate(
501 handle
502 .upgrade(cx)
503 .expect("model dropped with pending condition")
504 .read(cx),
505 cx,
506 ) {
507 break;
508 }
509 }
510
511 cx.borrow().foreground().start_waiting();
512 rx.next()
513 .await
514 .expect("model dropped with pending condition");
515 cx.borrow().foreground().finish_waiting();
516 }
517 })
518 .await
519 .expect("condition timed out");
520 drop(subscriptions);
521 }
522 }
523}
524
525impl<T: View> ViewHandle<T> {
526 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
527 use postage::prelude::{Sink as _, Stream as _};
528
529 let (mut tx, mut rx) = postage::mpsc::channel(1);
530 let mut cx = cx.cx.borrow_mut();
531 let subscription = cx.observe(self, move |_, _| {
532 tx.try_send(()).ok();
533 });
534
535 let duration = if std::env::var("CI").is_ok() {
536 Duration::from_secs(5)
537 } else {
538 Duration::from_secs(1)
539 };
540
541 async move {
542 let notification = crate::util::timeout(duration, rx.recv())
543 .await
544 .expect("next notification timed out");
545 drop(subscription);
546 notification.expect("model dropped while test was waiting for its next notification")
547 }
548 }
549
550 pub fn condition(
551 &self,
552 cx: &TestAppContext,
553 mut predicate: impl FnMut(&T, &AppContext) -> bool,
554 ) -> impl Future<Output = ()> {
555 use postage::prelude::{Sink as _, Stream as _};
556
557 let (tx, mut rx) = postage::mpsc::channel(1024);
558 let timeout_duration = cx.condition_duration();
559
560 let mut cx = cx.cx.borrow_mut();
561 let subscriptions = (
562 cx.observe(self, {
563 let mut tx = tx.clone();
564 move |_, _| {
565 tx.blocking_send(()).ok();
566 }
567 }),
568 cx.subscribe(self, {
569 let mut tx = tx.clone();
570 move |_, _, _| {
571 tx.blocking_send(()).ok();
572 }
573 }),
574 );
575
576 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
577 let handle = self.downgrade();
578
579 async move {
580 crate::util::timeout(timeout_duration, async move {
581 loop {
582 {
583 let cx = cx.borrow();
584 let cx = &*cx;
585 if predicate(
586 handle
587 .upgrade(cx)
588 .expect("view dropped with pending condition")
589 .read(cx),
590 cx,
591 ) {
592 break;
593 }
594 }
595
596 cx.borrow().foreground().start_waiting();
597 rx.recv()
598 .await
599 .expect("view dropped with pending condition");
600 cx.borrow().foreground().finish_waiting();
601 }
602 })
603 .await
604 .expect("condition timed out");
605 drop(subscriptions);
606 }
607 }
608}
609
610/// Tracks string context to be printed when assertions fail.
611/// Often this is done by storing a context string in the manager and returning the handle.
612#[derive(Clone)]
613pub struct AssertionContextManager {
614 id: Arc<AtomicUsize>,
615 contexts: Arc<RwLock<BTreeMap<usize, String>>>,
616}
617
618impl AssertionContextManager {
619 pub fn new() -> Self {
620 Self {
621 id: Arc::new(AtomicUsize::new(0)),
622 contexts: Arc::new(RwLock::new(BTreeMap::new())),
623 }
624 }
625
626 pub fn add_context(&self, context: String) -> ContextHandle {
627 let id = self.id.fetch_add(1, Ordering::Relaxed);
628 let mut contexts = self.contexts.write();
629 contexts.insert(id, context);
630 ContextHandle {
631 id,
632 manager: self.clone(),
633 }
634 }
635
636 pub fn context(&self) -> String {
637 let contexts = self.contexts.read();
638 format!("\n{}\n", contexts.values().join("\n"))
639 }
640}
641
642/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
643/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
644/// the state that was set initially for the failure can be printed in the error message
645pub struct ContextHandle {
646 id: usize,
647 manager: AssertionContextManager,
648}
649
650impl Drop for ContextHandle {
651 fn drop(&mut self) {
652 let mut contexts = self.manager.contexts.write();
653 contexts.remove(&self.id);
654 }
655}