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