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,
21 geometry::vector::Vector2F,
22 keymap_matcher::Keystroke,
23 platform,
24 platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform},
25 Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
26 ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext,
27 ViewHandle, WeakHandle,
28};
29use collections::BTreeMap;
30
31use super::{
32 ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
33};
34
35#[derive(Clone)]
36pub struct TestAppContext {
37 cx: Rc<RefCell<AppContext>>,
38 foreground_platform: Rc<platform::test::ForegroundPlatform>,
39 condition_duration: Option<Duration>,
40 pub function_name: String,
41 assertion_context: AssertionContextManager,
42}
43
44impl TestAppContext {
45 pub fn new(
46 foreground_platform: Rc<platform::test::ForegroundPlatform>,
47 platform: Arc<dyn Platform>,
48 foreground: Rc<executor::Foreground>,
49 background: Arc<executor::Background>,
50 font_cache: Arc<FontCache>,
51 leak_detector: Arc<Mutex<LeakDetector>>,
52 first_entity_id: usize,
53 function_name: String,
54 ) -> Self {
55 let mut cx = AppContext::new(
56 foreground,
57 background,
58 platform,
59 foreground_platform.clone(),
60 font_cache,
61 RefCounts::new(leak_detector),
62 (),
63 );
64 cx.next_entity_id = first_entity_id;
65 let cx = TestAppContext {
66 cx: Rc::new(RefCell::new(cx)),
67 foreground_platform,
68 condition_duration: None,
69 function_name,
70 assertion_context: AssertionContextManager::new(),
71 };
72 cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
73 cx
74 }
75
76 pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
77 let mut cx = self.cx.borrow_mut();
78 if let Some(view_id) = cx.focused_view_id(window_id) {
79 cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
80 }
81 }
82
83 pub fn dispatch_global_action<A: Action>(&self, action: A) {
84 self.cx.borrow_mut().dispatch_global_action(action);
85 }
86
87 pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
88 let handled = self.cx.borrow_mut().update(|cx| {
89 let presenter = cx
90 .presenters_and_platform_windows
91 .get(&window_id)
92 .unwrap()
93 .0
94 .clone();
95
96 if cx.dispatch_keystroke(window_id, &keystroke) {
97 return true;
98 }
99
100 if presenter.borrow_mut().dispatch_event(
101 Event::KeyDown(KeyDownEvent {
102 keystroke: keystroke.clone(),
103 is_held,
104 }),
105 false,
106 cx,
107 ) {
108 return true;
109 }
110
111 false
112 });
113
114 if !handled && !keystroke.cmd && !keystroke.ctrl {
115 WindowInputHandler {
116 app: self.cx.clone(),
117 window_id,
118 }
119 .replace_text_in_range(None, &keystroke.key)
120 }
121 }
122
123 pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
124 where
125 T: Entity,
126 F: FnOnce(&mut ModelContext<T>) -> T,
127 {
128 self.cx.borrow_mut().add_model(build_model)
129 }
130
131 pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
132 where
133 T: View,
134 F: FnOnce(&mut ViewContext<T>) -> T,
135 {
136 let (window_id, view) = self
137 .cx
138 .borrow_mut()
139 .add_window(Default::default(), build_root_view);
140 self.simulate_window_activation(Some(window_id));
141 (window_id, view)
142 }
143
144 pub fn add_view<T, F>(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> 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(&self, window_id: usize) -> Option<AnyViewHandle> {
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())
162 }
163
164 pub fn update<T, F: FnOnce(&mut AppContext) -> 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().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().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;
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;
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;
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;
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}