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