1use crate::{
2 AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context,
3 EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher,
4 TestPlatform, WindowContext,
5};
6use anyhow::{anyhow, bail};
7use futures::{Stream, StreamExt};
8use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration};
9
10#[derive(Clone)]
11pub struct TestAppContext {
12 pub app: Rc<RefCell<AppContext>>,
13 pub background_executor: BackgroundExecutor,
14 pub foreground_executor: ForegroundExecutor,
15}
16
17impl Context for TestAppContext {
18 type Result<T> = T;
19
20 fn build_model<T: 'static>(
21 &mut self,
22 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
23 ) -> Self::Result<Model<T>>
24 where
25 T: 'static,
26 {
27 let mut app = self.app.borrow_mut();
28 app.build_model(build_model)
29 }
30
31 fn update_model<T: 'static, R>(
32 &mut self,
33 handle: &Model<T>,
34 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
35 ) -> Self::Result<R> {
36 let mut app = self.app.borrow_mut();
37 app.update_model(handle, update)
38 }
39
40 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
41 where
42 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
43 {
44 let mut lock = self.app.borrow_mut();
45 lock.update_window(window, f)
46 }
47}
48
49impl TestAppContext {
50 pub fn new(dispatcher: TestDispatcher) -> Self {
51 let dispatcher = Arc::new(dispatcher);
52 let background_executor = BackgroundExecutor::new(dispatcher.clone());
53 let foreground_executor = ForegroundExecutor::new(dispatcher);
54 let platform = Rc::new(TestPlatform::new(
55 background_executor.clone(),
56 foreground_executor.clone(),
57 ));
58 let asset_source = Arc::new(());
59 let http_client = util::http::FakeHttpClient::with_404_response();
60 Self {
61 app: AppContext::new(platform, asset_source, http_client),
62 background_executor,
63 foreground_executor,
64 }
65 }
66
67 pub fn quit(&self) {
68 self.app.borrow_mut().quit();
69 }
70
71 pub fn refresh(&mut self) -> Result<()> {
72 let mut app = self.app.borrow_mut();
73 app.refresh();
74 Ok(())
75 }
76
77 pub fn executor(&self) -> &BackgroundExecutor {
78 &self.background_executor
79 }
80
81 pub fn foreground_executor(&self) -> &ForegroundExecutor {
82 &self.foreground_executor
83 }
84
85 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
86 let mut cx = self.app.borrow_mut();
87 cx.update(f)
88 }
89
90 pub fn update_window<R>(
91 &self,
92 handle: AnyWindowHandle,
93 update: impl FnOnce(AnyView, &mut WindowContext) -> R,
94 ) -> R {
95 let mut app = self.app.borrow_mut();
96 app.update_window(handle, update).unwrap()
97 }
98
99 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
100 where
101 Fut: Future<Output = R> + 'static,
102 R: 'static,
103 {
104 self.foreground_executor.spawn(f(self.to_async()))
105 }
106
107 pub fn has_global<G: 'static>(&self) -> bool {
108 let app = self.app.borrow();
109 app.has_global::<G>()
110 }
111
112 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
113 let app = self.app.borrow();
114 read(app.global(), &app)
115 }
116
117 pub fn try_read_global<G: 'static, R>(
118 &self,
119 read: impl FnOnce(&G, &AppContext) -> R,
120 ) -> Option<R> {
121 let lock = self.app.borrow();
122 Some(read(lock.try_global()?, &lock))
123 }
124
125 pub fn update_global<G: 'static, R>(
126 &mut self,
127 update: impl FnOnce(&mut G, &mut AppContext) -> R,
128 ) -> R {
129 let mut lock = self.app.borrow_mut();
130 lock.update_global(update)
131 }
132
133 pub fn to_async(&self) -> AsyncAppContext {
134 AsyncAppContext {
135 app: Rc::downgrade(&self.app),
136 background_executor: self.background_executor.clone(),
137 foreground_executor: self.foreground_executor.clone(),
138 }
139 }
140
141 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
142 let (tx, rx) = futures::channel::mpsc::unbounded();
143
144 entity.update(self, move |_, cx: &mut ModelContext<T>| {
145 cx.observe(entity, {
146 let tx = tx.clone();
147 move |_, _, _| {
148 let _ = tx.unbounded_send(());
149 }
150 })
151 .detach();
152
153 cx.on_release(move |_, _| tx.close_channel()).detach();
154 });
155
156 rx
157 }
158
159 pub fn events<T: 'static + EventEmitter>(
160 &mut self,
161 entity: &Model<T>,
162 ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
163 where
164 T::Event: 'static + Clone,
165 {
166 let (tx, rx) = futures::channel::mpsc::unbounded();
167 entity
168 .update(self, |_, cx: &mut ModelContext<T>| {
169 cx.subscribe(entity, move |_model, _handle, event, _cx| {
170 let _ = tx.unbounded_send(event.clone());
171 })
172 })
173 .detach();
174 rx
175 }
176
177 pub async fn condition<T: 'static>(
178 &mut self,
179 model: &Model<T>,
180 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
181 ) {
182 let timer = self.executor().timer(Duration::from_secs(3));
183 let mut notifications = self.notifications(model);
184
185 use futures::FutureExt as _;
186 use smol::future::FutureExt as _;
187
188 async {
189 while notifications.next().await.is_some() {
190 if model.update(self, &mut predicate) {
191 return Ok(());
192 }
193 }
194 bail!("model dropped")
195 }
196 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
197 .await
198 .unwrap();
199 }
200}