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