1use crate::{
2 AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
3 EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
4 Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext,
5 WindowHandle, WindowOptions,
6};
7use anyhow::{anyhow, bail};
8use futures::{Stream, StreamExt};
9use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
10
11#[derive(Clone)]
12pub struct TestAppContext {
13 pub app: Rc<AppCell>,
14 pub background_executor: BackgroundExecutor,
15 pub foreground_executor: ForegroundExecutor,
16 pub dispatcher: TestDispatcher,
17}
18
19impl Context for TestAppContext {
20 type Result<T> = T;
21
22 fn build_model<T: 'static>(
23 &mut self,
24 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
25 ) -> Self::Result<Model<T>>
26 where
27 T: 'static,
28 {
29 let mut app = self.app.borrow_mut();
30 app.build_model(build_model)
31 }
32
33 fn update_model<T: 'static, R>(
34 &mut self,
35 handle: &Model<T>,
36 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
37 ) -> Self::Result<R> {
38 let mut app = self.app.borrow_mut();
39 app.update_model(handle, update)
40 }
41
42 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
43 where
44 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
45 {
46 let mut lock = self.app.borrow_mut();
47 lock.update_window(window, f)
48 }
49
50 fn read_model<T, R>(
51 &self,
52 handle: &Model<T>,
53 read: impl FnOnce(&T, &AppContext) -> R,
54 ) -> Self::Result<R>
55 where
56 T: 'static,
57 {
58 let app = self.app.borrow();
59 app.read_model(handle, read)
60 }
61
62 fn read_window<R>(
63 &self,
64 window: &AnyWindowHandle,
65 read: impl FnOnce(AnyView, &AppContext) -> R,
66 ) -> Result<R> {
67 let app = self.app.borrow();
68 app.read_window(window, read)
69 }
70}
71
72impl TestAppContext {
73 pub fn new(dispatcher: TestDispatcher) -> Self {
74 let arc_dispatcher = Arc::new(dispatcher.clone());
75 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
76 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
77 let platform = Rc::new(TestPlatform::new(
78 background_executor.clone(),
79 foreground_executor.clone(),
80 ));
81 let asset_source = Arc::new(());
82 let http_client = util::http::FakeHttpClient::with_404_response();
83 Self {
84 app: AppContext::new(platform, asset_source, http_client),
85 background_executor,
86 foreground_executor,
87 dispatcher: dispatcher.clone(),
88 }
89 }
90
91 pub fn new_app(&self) -> TestAppContext {
92 Self::new(self.dispatcher.clone())
93 }
94
95 pub fn quit(&self) {
96 self.app.borrow_mut().quit();
97 }
98
99 pub fn refresh(&mut self) -> Result<()> {
100 let mut app = self.app.borrow_mut();
101 app.refresh();
102 Ok(())
103 }
104
105 pub fn executor(&self) -> &BackgroundExecutor {
106 &self.background_executor
107 }
108
109 pub fn foreground_executor(&self) -> &ForegroundExecutor {
110 &self.foreground_executor
111 }
112
113 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
114 let mut cx = self.app.borrow_mut();
115 cx.update(f)
116 }
117
118 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
119 let cx = self.app.borrow();
120 f(&*cx)
121 }
122
123 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
124 where
125 F: FnOnce(&mut ViewContext<V>) -> V,
126 V: Render,
127 {
128 let mut cx = self.app.borrow_mut();
129 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
130 }
131
132 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
133 where
134 Fut: Future<Output = R> + 'static,
135 R: 'static,
136 {
137 self.foreground_executor.spawn(f(self.to_async()))
138 }
139
140 pub fn has_global<G: 'static>(&self) -> bool {
141 let app = self.app.borrow();
142 app.has_global::<G>()
143 }
144
145 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
146 let app = self.app.borrow();
147 read(app.global(), &app)
148 }
149
150 pub fn try_read_global<G: 'static, R>(
151 &self,
152 read: impl FnOnce(&G, &AppContext) -> R,
153 ) -> Option<R> {
154 let lock = self.app.borrow();
155 Some(read(lock.try_global()?, &lock))
156 }
157
158 pub fn set_global<G: 'static, R>(&mut self, global: G) {
159 let mut lock = self.app.borrow_mut();
160 lock.set_global(global);
161 }
162
163 pub fn update_global<G: 'static, R>(
164 &mut self,
165 update: impl FnOnce(&mut G, &mut AppContext) -> R,
166 ) -> R {
167 let mut lock = self.app.borrow_mut();
168 lock.update_global(update)
169 }
170
171 pub fn to_async(&self) -> AsyncAppContext {
172 AsyncAppContext {
173 app: Rc::downgrade(&self.app),
174 background_executor: self.background_executor.clone(),
175 foreground_executor: self.foreground_executor.clone(),
176 }
177 }
178
179 pub fn dispatch_keystroke(
180 &mut self,
181 window: AnyWindowHandle,
182 keystroke: Keystroke,
183 is_held: bool,
184 ) {
185 let handled = window
186 .update(self, |_, cx| {
187 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
188 })
189 .is_ok_and(|handled| handled);
190
191 if !handled {
192 // todo!() simluate input here
193 }
194 }
195
196 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
197 let (tx, rx) = futures::channel::mpsc::unbounded();
198
199 entity.update(self, move |_, cx: &mut ModelContext<T>| {
200 cx.observe(entity, {
201 let tx = tx.clone();
202 move |_, _, _| {
203 let _ = tx.unbounded_send(());
204 }
205 })
206 .detach();
207
208 cx.on_release(move |_, _| tx.close_channel()).detach();
209 });
210
211 rx
212 }
213
214 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
215 &mut self,
216 entity: &Model<T>,
217 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
218 where
219 Evt: 'static + Clone,
220 {
221 let (tx, rx) = futures::channel::mpsc::unbounded();
222 entity
223 .update(self, |_, cx: &mut ModelContext<T>| {
224 cx.subscribe(entity, move |_model, _handle, event, _cx| {
225 let _ = tx.unbounded_send(event.clone());
226 })
227 })
228 .detach();
229 rx
230 }
231
232 pub async fn condition<T: 'static>(
233 &mut self,
234 model: &Model<T>,
235 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
236 ) {
237 let timer = self.executor().timer(Duration::from_secs(3));
238 let mut notifications = self.notifications(model);
239
240 use futures::FutureExt as _;
241 use smol::future::FutureExt as _;
242
243 async {
244 while notifications.next().await.is_some() {
245 if model.update(self, &mut predicate) {
246 return Ok(());
247 }
248 }
249 bail!("model dropped")
250 }
251 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
252 .await
253 .unwrap();
254 }
255}
256
257impl<T: Send> Model<T> {
258 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
259 where
260 Evt: Send + Clone + 'static,
261 T: EventEmitter<Evt>,
262 {
263 let (tx, mut rx) = futures::channel::mpsc::unbounded();
264 let _subscription = self.update(cx, |_, cx| {
265 cx.subscribe(self, move |_, _, event, _| {
266 tx.unbounded_send(event.clone()).ok();
267 })
268 });
269
270 cx.executor().run_until_parked();
271 rx.try_next()
272 .expect("no event received")
273 .expect("model was dropped")
274 }
275}