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