1use std::{
2 cell::RefCell,
3 marker::PhantomData,
4 mem,
5 path::PathBuf,
6 rc::Rc,
7 sync::{
8 atomic::{AtomicUsize, Ordering},
9 Arc,
10 },
11 time::Duration,
12};
13
14use futures::Future;
15use itertools::Itertools;
16use parking_lot::{Mutex, RwLock};
17use smol::stream::StreamExt;
18
19use crate::{
20 executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
21 AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, Handle, InputHandler,
22 KeyDownEvent, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
23 ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
24 WeakHandle,
25};
26use collections::BTreeMap;
27
28use super::{
29 ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
30};
31
32pub struct TestAppContext {
33 cx: Rc<RefCell<MutableAppContext>>,
34 foreground_platform: Rc<platform::test::ForegroundPlatform>,
35 condition_duration: Option<Duration>,
36 pub function_name: String,
37 assertion_context: AssertionContextManager,
38}
39
40impl TestAppContext {
41 pub fn new(
42 foreground_platform: Rc<platform::test::ForegroundPlatform>,
43 platform: Arc<dyn Platform>,
44 foreground: Rc<executor::Foreground>,
45 background: Arc<executor::Background>,
46 font_cache: Arc<FontCache>,
47 leak_detector: Arc<Mutex<LeakDetector>>,
48 first_entity_id: usize,
49 function_name: String,
50 ) -> Self {
51 let mut cx = MutableAppContext::new(
52 foreground,
53 background,
54 platform,
55 foreground_platform.clone(),
56 font_cache,
57 RefCounts::new(leak_detector),
58 (),
59 );
60 cx.next_entity_id = first_entity_id;
61 let cx = TestAppContext {
62 cx: Rc::new(RefCell::new(cx)),
63 foreground_platform,
64 condition_duration: None,
65 function_name,
66 assertion_context: AssertionContextManager::new(),
67 };
68 cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
69 cx
70 }
71
72 pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
73 let mut cx = self.cx.borrow_mut();
74 if let Some(view_id) = cx.focused_view_id(window_id) {
75 cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
76 }
77 }
78
79 pub fn dispatch_global_action<A: Action>(&self, action: A) {
80 self.cx.borrow_mut().dispatch_global_action(action);
81 }
82
83 pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
84 let handled = self.cx.borrow_mut().update(|cx| {
85 let presenter = cx
86 .presenters_and_platform_windows
87 .get(&window_id)
88 .unwrap()
89 .0
90 .clone();
91
92 if cx.dispatch_keystroke(window_id, &keystroke) {
93 return true;
94 }
95
96 if presenter.borrow_mut().dispatch_event(
97 Event::KeyDown(KeyDownEvent {
98 keystroke: keystroke.clone(),
99 is_held,
100 }),
101 false,
102 cx,
103 ) {
104 return true;
105 }
106
107 false
108 });
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 add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
120 where
121 T: Entity,
122 F: FnOnce(&mut ModelContext<T>) -> T,
123 {
124 self.cx.borrow_mut().add_model(build_model)
125 }
126
127 pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
128 where
129 T: View,
130 F: FnOnce(&mut ViewContext<T>) -> T,
131 {
132 let (window_id, view) = self
133 .cx
134 .borrow_mut()
135 .add_window(Default::default(), build_root_view);
136 self.simulate_window_activation(Some(window_id));
137 (window_id, view)
138 }
139
140 pub fn add_view<T, F>(
141 &mut self,
142 parent_handle: impl Into<AnyViewHandle>,
143 build_view: F,
144 ) -> ViewHandle<T>
145 where
146 T: View,
147 F: FnOnce(&mut ViewContext<T>) -> T,
148 {
149 self.cx.borrow_mut().add_view(parent_handle, build_view)
150 }
151
152 pub fn window_ids(&self) -> Vec<usize> {
153 self.cx.borrow().window_ids().collect()
154 }
155
156 pub fn root_view<T: View>(&self, window_id: usize) -> Option<ViewHandle<T>> {
157 self.cx.borrow().root_view(window_id)
158 }
159
160 pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
161 callback(self.cx.borrow().as_ref())
162 }
163
164 pub fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
165 let mut state = self.cx.borrow_mut();
166 // Don't increment pending flushes in order for effects to be flushed before the callback
167 // completes, which is helpful in tests.
168 let result = callback(&mut *state);
169 // Flush effects after the callback just in case there are any. This can happen in edge
170 // cases such as the closure dropping handles.
171 state.flush_effects();
172 result
173 }
174
175 pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
176 where
177 F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
178 V: View,
179 {
180 handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
181 let mut render_cx = RenderContext {
182 app: cx,
183 window_id: handle.window_id(),
184 view_id: handle.id(),
185 view_type: PhantomData,
186 titlebar_height: 0.,
187 hovered_region_ids: Default::default(),
188 clicked_region_ids: None,
189 refreshing: false,
190 appearance: Appearance::Light,
191 };
192 f(view, &mut render_cx)
193 })
194 }
195
196 pub fn to_async(&self) -> AsyncAppContext {
197 AsyncAppContext(self.cx.clone())
198 }
199
200 pub fn font_cache(&self) -> Arc<FontCache> {
201 self.cx.borrow().cx.font_cache.clone()
202 }
203
204 pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
205 self.foreground_platform.clone()
206 }
207
208 pub fn platform(&self) -> Arc<dyn platform::Platform> {
209 self.cx.borrow().cx.platform.clone()
210 }
211
212 pub fn foreground(&self) -> Rc<executor::Foreground> {
213 self.cx.borrow().foreground().clone()
214 }
215
216 pub fn background(&self) -> Arc<executor::Background> {
217 self.cx.borrow().background().clone()
218 }
219
220 pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
221 where
222 F: FnOnce(AsyncAppContext) -> Fut,
223 Fut: 'static + Future<Output = T>,
224 T: 'static,
225 {
226 let foreground = self.foreground();
227 let future = f(self.to_async());
228 let cx = self.to_async();
229 foreground.spawn(async move {
230 let result = future.await;
231 cx.0.borrow_mut().flush_effects();
232 result
233 })
234 }
235
236 pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
237 self.foreground_platform.simulate_new_path_selection(result);
238 }
239
240 pub fn did_prompt_for_new_path(&self) -> bool {
241 self.foreground_platform.as_ref().did_prompt_for_new_path()
242 }
243
244 pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
245 use postage::prelude::Sink as _;
246
247 let mut done_tx = self
248 .window_mut(window_id)
249 .pending_prompts
250 .borrow_mut()
251 .pop_front()
252 .expect("prompt was not called");
253 let _ = done_tx.try_send(answer);
254 }
255
256 pub fn has_pending_prompt(&self, window_id: usize) -> bool {
257 let window = self.window_mut(window_id);
258 let prompts = window.pending_prompts.borrow_mut();
259 !prompts.is_empty()
260 }
261
262 pub fn current_window_title(&self, window_id: usize) -> Option<String> {
263 self.window_mut(window_id).title.clone()
264 }
265
266 pub fn simulate_window_close(&self, window_id: usize) -> bool {
267 let handler = self.window_mut(window_id).should_close_handler.take();
268 if let Some(mut handler) = handler {
269 let should_close = handler();
270 self.window_mut(window_id).should_close_handler = Some(handler);
271 should_close
272 } else {
273 false
274 }
275 }
276
277 pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
278 let mut window = self.window_mut(window_id);
279 window.size = size;
280 let mut handlers = mem::take(&mut window.resize_handlers);
281 drop(window);
282 for handler in &mut handlers {
283 handler();
284 }
285 self.window_mut(window_id).resize_handlers = handlers;
286 }
287
288 pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
289 let mut handlers = BTreeMap::new();
290 {
291 let mut cx = self.cx.borrow_mut();
292 for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows {
293 let window = window
294 .as_any_mut()
295 .downcast_mut::<platform::test::Window>()
296 .unwrap();
297 handlers.insert(
298 *window_id,
299 mem::take(&mut window.active_status_change_handlers),
300 );
301 }
302 };
303 let mut handlers = handlers.into_iter().collect::<Vec<_>>();
304 handlers.sort_unstable_by_key(|(window_id, _)| Some(*window_id) == to_activate);
305
306 for (window_id, mut window_handlers) in handlers {
307 for window_handler in &mut window_handlers {
308 window_handler(Some(window_id) == to_activate);
309 }
310
311 self.window_mut(window_id)
312 .active_status_change_handlers
313 .extend(window_handlers);
314 }
315 }
316
317 pub fn is_window_edited(&self, window_id: usize) -> bool {
318 self.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 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
344 .presenters_and_platform_windows
345 .get_mut(&window_id)
346 .unwrap();
347 let test_window = 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 UpdateModel for TestAppContext {
387 fn update_model<T: Entity, O>(
388 &mut self,
389 handle: &ModelHandle<T>,
390 update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> O,
391 ) -> O {
392 self.cx.borrow_mut().update_model(handle, update)
393 }
394}
395
396impl ReadModelWith for TestAppContext {
397 fn read_model_with<E: Entity, T>(
398 &self,
399 handle: &ModelHandle<E>,
400 read: &mut dyn FnMut(&E, &AppContext) -> T,
401 ) -> T {
402 let cx = self.cx.borrow();
403 let cx = cx.as_ref();
404 read(handle.read(cx), cx)
405 }
406}
407
408impl UpdateView for TestAppContext {
409 fn update_view<T, S>(
410 &mut self,
411 handle: &ViewHandle<T>,
412 update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
413 ) -> S
414 where
415 T: View,
416 {
417 self.cx.borrow_mut().update_view(handle, update)
418 }
419}
420
421impl ReadViewWith for TestAppContext {
422 fn read_view_with<V, T>(
423 &self,
424 handle: &ViewHandle<V>,
425 read: &mut dyn FnMut(&V, &AppContext) -> T,
426 ) -> T
427 where
428 V: View,
429 {
430 let cx = self.cx.borrow();
431 let cx = cx.as_ref();
432 read(handle.read(cx), cx)
433 }
434}
435
436impl<T: Entity> ModelHandle<T> {
437 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
438 let (tx, mut rx) = futures::channel::mpsc::unbounded();
439 let mut cx = cx.cx.borrow_mut();
440 let subscription = cx.observe(self, move |_, _| {
441 tx.unbounded_send(()).ok();
442 });
443
444 let duration = if std::env::var("CI").is_ok() {
445 Duration::from_secs(5)
446 } else {
447 Duration::from_secs(1)
448 };
449
450 async move {
451 let notification = crate::util::timeout(duration, rx.next())
452 .await
453 .expect("next notification timed out");
454 drop(subscription);
455 notification.expect("model dropped while test was waiting for its next notification")
456 }
457 }
458
459 pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
460 where
461 T::Event: Clone,
462 {
463 let (tx, mut rx) = futures::channel::mpsc::unbounded();
464 let mut cx = cx.cx.borrow_mut();
465 let subscription = cx.subscribe(self, move |_, event, _| {
466 tx.unbounded_send(event.clone()).ok();
467 });
468
469 let duration = if std::env::var("CI").is_ok() {
470 Duration::from_secs(5)
471 } else {
472 Duration::from_secs(1)
473 };
474
475 cx.foreground.start_waiting();
476 async move {
477 let event = crate::util::timeout(duration, rx.next())
478 .await
479 .expect("next event timed out");
480 drop(subscription);
481 event.expect("model dropped while test was waiting for its next event")
482 }
483 }
484
485 pub fn condition(
486 &self,
487 cx: &TestAppContext,
488 mut predicate: impl FnMut(&T, &AppContext) -> bool,
489 ) -> impl Future<Output = ()> {
490 let (tx, mut rx) = futures::channel::mpsc::unbounded();
491
492 let mut cx = cx.cx.borrow_mut();
493 let subscriptions = (
494 cx.observe(self, {
495 let tx = tx.clone();
496 move |_, _| {
497 tx.unbounded_send(()).ok();
498 }
499 }),
500 cx.subscribe(self, {
501 move |_, _, _| {
502 tx.unbounded_send(()).ok();
503 }
504 }),
505 );
506
507 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
508 let handle = self.downgrade();
509 let duration = if std::env::var("CI").is_ok() {
510 Duration::from_secs(5)
511 } else {
512 Duration::from_secs(1)
513 };
514
515 async move {
516 crate::util::timeout(duration, async move {
517 loop {
518 {
519 let cx = cx.borrow();
520 let cx = cx.as_ref();
521 if predicate(
522 handle
523 .upgrade(cx)
524 .expect("model dropped with pending condition")
525 .read(cx),
526 cx,
527 ) {
528 break;
529 }
530 }
531
532 cx.borrow().foreground().start_waiting();
533 rx.next()
534 .await
535 .expect("model dropped with pending condition");
536 cx.borrow().foreground().finish_waiting();
537 }
538 })
539 .await
540 .expect("condition timed out");
541 drop(subscriptions);
542 }
543 }
544}
545
546impl<T: View> ViewHandle<T> {
547 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
548 use postage::prelude::{Sink as _, Stream as _};
549
550 let (mut tx, mut rx) = postage::mpsc::channel(1);
551 let mut cx = cx.cx.borrow_mut();
552 let subscription = cx.observe(self, move |_, _| {
553 tx.try_send(()).ok();
554 });
555
556 let duration = if std::env::var("CI").is_ok() {
557 Duration::from_secs(5)
558 } else {
559 Duration::from_secs(1)
560 };
561
562 async move {
563 let notification = crate::util::timeout(duration, rx.recv())
564 .await
565 .expect("next notification timed out");
566 drop(subscription);
567 notification.expect("model dropped while test was waiting for its next notification")
568 }
569 }
570
571 pub fn condition(
572 &self,
573 cx: &TestAppContext,
574 mut predicate: impl FnMut(&T, &AppContext) -> bool,
575 ) -> impl Future<Output = ()> {
576 use postage::prelude::{Sink as _, Stream as _};
577
578 let (tx, mut rx) = postage::mpsc::channel(1024);
579 let timeout_duration = cx.condition_duration();
580
581 let mut cx = cx.cx.borrow_mut();
582 let subscriptions = self.update(&mut *cx, |_, cx| {
583 (
584 cx.observe(self, {
585 let mut tx = tx.clone();
586 move |_, _, _| {
587 tx.blocking_send(()).ok();
588 }
589 }),
590 cx.subscribe(self, {
591 let mut tx = tx.clone();
592 move |_, _, _, _| {
593 tx.blocking_send(()).ok();
594 }
595 }),
596 )
597 });
598
599 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
600 let handle = self.downgrade();
601
602 async move {
603 crate::util::timeout(timeout_duration, async move {
604 loop {
605 {
606 let cx = cx.borrow();
607 let cx = cx.as_ref();
608 if predicate(
609 handle
610 .upgrade(cx)
611 .expect("view dropped with pending condition")
612 .read(cx),
613 cx,
614 ) {
615 break;
616 }
617 }
618
619 cx.borrow().foreground().start_waiting();
620 rx.recv()
621 .await
622 .expect("view dropped with pending condition");
623 cx.borrow().foreground().finish_waiting();
624 }
625 })
626 .await
627 .expect("condition timed out");
628 drop(subscriptions);
629 }
630 }
631}
632
633/// Tracks string context to be printed when assertions fail.
634/// Often this is done by storing a context string in the manager and returning the handle.
635#[derive(Clone)]
636pub struct AssertionContextManager {
637 id: Arc<AtomicUsize>,
638 contexts: Arc<RwLock<BTreeMap<usize, String>>>,
639}
640
641impl AssertionContextManager {
642 pub fn new() -> Self {
643 Self {
644 id: Arc::new(AtomicUsize::new(0)),
645 contexts: Arc::new(RwLock::new(BTreeMap::new())),
646 }
647 }
648
649 pub fn add_context(&self, context: String) -> ContextHandle {
650 let id = self.id.fetch_add(1, Ordering::Relaxed);
651 let mut contexts = self.contexts.write();
652 contexts.insert(id, context);
653 ContextHandle {
654 id,
655 manager: self.clone(),
656 }
657 }
658
659 pub fn context(&self) -> String {
660 let contexts = self.contexts.read();
661 format!("\n{}\n", contexts.values().join("\n"))
662 }
663}
664
665/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
666/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
667/// the state that was set initially for the failure can be printed in the error message
668pub struct ContextHandle {
669 id: usize,
670 manager: AssertionContextManager,
671}
672
673impl Drop for ContextHandle {
674 fn drop(&mut self) {
675 let mut contexts = self.manager.contexts.write();
676 contexts.remove(&self.id);
677 }
678}