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