1use crate::{
2 AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
3 EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
4 Result, Task, TestDispatcher, TestPlatform, WindowContext,
5};
6use anyhow::{anyhow, bail};
7use futures::{Stream, StreamExt};
8use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
9
10#[derive(Clone)]
11pub struct TestAppContext {
12 pub app: Rc<AppCell>,
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 spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
91 where
92 Fut: Future<Output = R> + 'static,
93 R: 'static,
94 {
95 self.foreground_executor.spawn(f(self.to_async()))
96 }
97
98 pub fn has_global<G: 'static>(&self) -> bool {
99 let app = self.app.borrow();
100 app.has_global::<G>()
101 }
102
103 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
104 let app = self.app.borrow();
105 read(app.global(), &app)
106 }
107
108 pub fn try_read_global<G: 'static, R>(
109 &self,
110 read: impl FnOnce(&G, &AppContext) -> R,
111 ) -> Option<R> {
112 let lock = self.app.borrow();
113 Some(read(lock.try_global()?, &lock))
114 }
115
116 pub fn update_global<G: 'static, R>(
117 &mut self,
118 update: impl FnOnce(&mut G, &mut AppContext) -> R,
119 ) -> R {
120 let mut lock = self.app.borrow_mut();
121 lock.update_global(update)
122 }
123
124 pub fn to_async(&self) -> AsyncAppContext {
125 AsyncAppContext {
126 app: Rc::downgrade(&self.app),
127 background_executor: self.background_executor.clone(),
128 foreground_executor: self.foreground_executor.clone(),
129 }
130 }
131
132 pub fn dispatch_keystroke(
133 &mut self,
134 window: AnyWindowHandle,
135 keystroke: Keystroke,
136 is_held: bool,
137 ) {
138 let handled = window
139 .update(self, |_, cx| {
140 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
141 })
142 .is_ok_and(|handled| handled);
143
144 if !handled {
145 // todo!() simluate input here
146 }
147 }
148
149 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
150 let (tx, rx) = futures::channel::mpsc::unbounded();
151
152 entity.update(self, move |_, cx: &mut ModelContext<T>| {
153 cx.observe(entity, {
154 let tx = tx.clone();
155 move |_, _, _| {
156 let _ = tx.unbounded_send(());
157 }
158 })
159 .detach();
160
161 cx.on_release(move |_, _| tx.close_channel()).detach();
162 });
163
164 rx
165 }
166
167 pub fn events<T: 'static + EventEmitter>(
168 &mut self,
169 entity: &Model<T>,
170 ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
171 where
172 T::Event: 'static + Clone,
173 {
174 let (tx, rx) = futures::channel::mpsc::unbounded();
175 entity
176 .update(self, |_, cx: &mut ModelContext<T>| {
177 cx.subscribe(entity, move |_model, _handle, event, _cx| {
178 let _ = tx.unbounded_send(event.clone());
179 })
180 })
181 .detach();
182 rx
183 }
184
185 pub async fn condition<T: 'static>(
186 &mut self,
187 model: &Model<T>,
188 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
189 ) {
190 let timer = self.executor().timer(Duration::from_secs(3));
191 let mut notifications = self.notifications(model);
192
193 use futures::FutureExt as _;
194 use smol::future::FutureExt as _;
195
196 async {
197 while notifications.next().await.is_some() {
198 if model.update(self, &mut predicate) {
199 return Ok(());
200 }
201 }
202 bail!("model dropped")
203 }
204 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
205 .await
206 .unwrap();
207 }
208}
209
210impl<T: Send + EventEmitter> Model<T> {
211 pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event
212 where
213 T::Event: Send + Clone,
214 {
215 let (tx, mut rx) = futures::channel::mpsc::unbounded();
216 let _subscription = self.update(cx, |_, cx| {
217 cx.subscribe(self, move |_, _, event, _| {
218 tx.unbounded_send(event.clone()).ok();
219 })
220 });
221
222 cx.executor().run_until_parked();
223 rx.try_next()
224 .expect("no event received")
225 .expect("model was dropped")
226 }
227}