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
63impl TestAppContext {
64 pub fn new(dispatcher: TestDispatcher) -> Self {
65 let arc_dispatcher = Arc::new(dispatcher.clone());
66 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
67 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
68 let platform = Rc::new(TestPlatform::new(
69 background_executor.clone(),
70 foreground_executor.clone(),
71 ));
72 let asset_source = Arc::new(());
73 let http_client = util::http::FakeHttpClient::with_404_response();
74 Self {
75 app: AppContext::new(platform, asset_source, http_client),
76 background_executor,
77 foreground_executor,
78 dispatcher: dispatcher.clone(),
79 }
80 }
81
82 pub fn new_app(&self) -> TestAppContext {
83 Self::new(self.dispatcher.clone())
84 }
85
86 pub fn quit(&self) {
87 self.app.borrow_mut().quit();
88 }
89
90 pub fn refresh(&mut self) -> Result<()> {
91 let mut app = self.app.borrow_mut();
92 app.refresh();
93 Ok(())
94 }
95
96 pub fn executor(&self) -> &BackgroundExecutor {
97 &self.background_executor
98 }
99
100 pub fn foreground_executor(&self) -> &ForegroundExecutor {
101 &self.foreground_executor
102 }
103
104 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
105 let mut cx = self.app.borrow_mut();
106 cx.update(f)
107 }
108
109 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
110 let cx = self.app.borrow();
111 f(&*cx)
112 }
113
114 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
115 where
116 F: FnOnce(&mut ViewContext<V>) -> V,
117 V: Render,
118 {
119 let mut cx = self.app.borrow_mut();
120 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
121 }
122
123 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
124 where
125 Fut: Future<Output = R> + 'static,
126 R: 'static,
127 {
128 self.foreground_executor.spawn(f(self.to_async()))
129 }
130
131 pub fn has_global<G: 'static>(&self) -> bool {
132 let app = self.app.borrow();
133 app.has_global::<G>()
134 }
135
136 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
137 let app = self.app.borrow();
138 read(app.global(), &app)
139 }
140
141 pub fn try_read_global<G: 'static, R>(
142 &self,
143 read: impl FnOnce(&G, &AppContext) -> R,
144 ) -> Option<R> {
145 let lock = self.app.borrow();
146 Some(read(lock.try_global()?, &lock))
147 }
148
149 pub fn update_global<G: 'static, R>(
150 &mut self,
151 update: impl FnOnce(&mut G, &mut AppContext) -> R,
152 ) -> R {
153 let mut lock = self.app.borrow_mut();
154 lock.update_global(update)
155 }
156
157 pub fn to_async(&self) -> AsyncAppContext {
158 AsyncAppContext {
159 app: Rc::downgrade(&self.app),
160 background_executor: self.background_executor.clone(),
161 foreground_executor: self.foreground_executor.clone(),
162 }
163 }
164
165 pub fn dispatch_keystroke(
166 &mut self,
167 window: AnyWindowHandle,
168 keystroke: Keystroke,
169 is_held: bool,
170 ) {
171 let handled = window
172 .update(self, |_, cx| {
173 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
174 })
175 .is_ok_and(|handled| handled);
176
177 if !handled {
178 // todo!() simluate input here
179 }
180 }
181
182 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
183 let (tx, rx) = futures::channel::mpsc::unbounded();
184
185 entity.update(self, move |_, cx: &mut ModelContext<T>| {
186 cx.observe(entity, {
187 let tx = tx.clone();
188 move |_, _, _| {
189 let _ = tx.unbounded_send(());
190 }
191 })
192 .detach();
193
194 cx.on_release(move |_, _| tx.close_channel()).detach();
195 });
196
197 rx
198 }
199
200 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
201 &mut self,
202 entity: &Model<T>,
203 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
204 where
205 Evt: 'static + Clone,
206 {
207 let (tx, rx) = futures::channel::mpsc::unbounded();
208 entity
209 .update(self, |_, cx: &mut ModelContext<T>| {
210 cx.subscribe(entity, move |_model, _handle, event, _cx| {
211 let _ = tx.unbounded_send(event.clone());
212 })
213 })
214 .detach();
215 rx
216 }
217
218 pub async fn condition<T: 'static>(
219 &mut self,
220 model: &Model<T>,
221 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
222 ) {
223 let timer = self.executor().timer(Duration::from_secs(3));
224 let mut notifications = self.notifications(model);
225
226 use futures::FutureExt as _;
227 use smol::future::FutureExt as _;
228
229 async {
230 while notifications.next().await.is_some() {
231 if model.update(self, &mut predicate) {
232 return Ok(());
233 }
234 }
235 bail!("model dropped")
236 }
237 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
238 .await
239 .unwrap();
240 }
241}
242
243impl<T: Send> Model<T> {
244 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
245 where
246 Evt: Send + Clone + 'static,
247 T: EventEmitter<Evt>,
248 {
249 let (tx, mut rx) = futures::channel::mpsc::unbounded();
250 let _subscription = self.update(cx, |_, cx| {
251 cx.subscribe(self, move |_, _, event, _| {
252 tx.unbounded_send(event.clone()).ok();
253 })
254 });
255
256 cx.executor().run_until_parked();
257 rx.try_next()
258 .expect("no event received")
259 .expect("model was dropped")
260 }
261}