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