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