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