gpui_navigator/
lifecycle.rs1use crate::NavigationRequest;
4use gpui::App;
5use std::future::Future;
6use std::pin::Pin;
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum LifecycleResult {
11 Continue,
13
14 Abort { reason: String },
16
17 Redirect { to: String },
19}
20
21impl LifecycleResult {
22 pub fn cont() -> Self {
24 Self::Continue
25 }
26
27 pub fn abort(reason: impl Into<String>) -> Self {
29 Self::Abort {
30 reason: reason.into(),
31 }
32 }
33
34 pub fn redirect(to: impl Into<String>) -> Self {
36 Self::Redirect { to: to.into() }
37 }
38
39 pub fn allows_continue(&self) -> bool {
41 matches!(self, LifecycleResult::Continue)
42 }
43
44 pub fn is_abort(&self) -> bool {
46 matches!(self, LifecycleResult::Abort { .. })
47 }
48
49 pub fn is_redirect(&self) -> bool {
51 matches!(self, LifecycleResult::Redirect { .. })
52 }
53}
54
55pub trait RouteLifecycle: Send + Sync + 'static {
90 type Future: Future<Output = LifecycleResult> + Send + 'static;
92
93 fn on_enter(&self, cx: &App, request: &NavigationRequest) -> Self::Future;
104
105 fn on_exit(&self, cx: &App) -> Self::Future;
114
115 fn can_deactivate(&self, cx: &App) -> Self::Future;
124}
125
126pub type BoxedLifecycle =
128 Box<dyn RouteLifecycle<Future = Pin<Box<dyn Future<Output = LifecycleResult> + Send>>>>;
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use gpui::TestAppContext;
134
135 struct TestLifecycle {
136 should_abort: bool,
137 should_redirect: bool,
138 }
139
140 impl RouteLifecycle for TestLifecycle {
141 type Future = Pin<Box<dyn Future<Output = LifecycleResult> + Send>>;
142
143 fn on_enter(&self, _cx: &App, _request: &NavigationRequest) -> Self::Future {
144 if self.should_abort {
145 Box::pin(async { LifecycleResult::abort("Test abort") })
146 } else if self.should_redirect {
147 Box::pin(async { LifecycleResult::redirect("/redirect") })
148 } else {
149 Box::pin(async { LifecycleResult::Continue })
150 }
151 }
152
153 fn on_exit(&self, _cx: &App) -> Self::Future {
154 Box::pin(async { LifecycleResult::Continue })
155 }
156
157 fn can_deactivate(&self, _cx: &App) -> Self::Future {
158 if self.should_abort {
159 Box::pin(async { LifecycleResult::abort("Cannot leave") })
160 } else {
161 Box::pin(async { LifecycleResult::Continue })
162 }
163 }
164 }
165
166 #[gpui::test]
167 fn test_lifecycle_result_continue(_cx: &mut TestAppContext) {
168 let result = LifecycleResult::Continue;
169 assert!(result.allows_continue());
170 assert!(!result.is_abort());
171 assert!(!result.is_redirect());
172 }
173
174 #[gpui::test]
175 fn test_lifecycle_result_abort(_cx: &mut TestAppContext) {
176 let result = LifecycleResult::abort("Test");
177 assert!(!result.allows_continue());
178 assert!(result.is_abort());
179 assert!(!result.is_redirect());
180 }
181
182 #[gpui::test]
183 fn test_lifecycle_result_redirect(_cx: &mut TestAppContext) {
184 let result = LifecycleResult::redirect("/test");
185 assert!(!result.allows_continue());
186 assert!(!result.is_abort());
187 assert!(result.is_redirect());
188 }
189
190 #[gpui::test]
191 fn test_lifecycle_on_enter_continue(cx: &mut TestAppContext) {
192 let lifecycle = TestLifecycle {
193 should_abort: false,
194 should_redirect: false,
195 };
196 let request = NavigationRequest::new("/test".to_string());
197
198 let result = cx.update(|cx| pollster::block_on(lifecycle.on_enter(cx, &request)));
199
200 assert_eq!(result, LifecycleResult::Continue);
201 }
202
203 #[gpui::test]
204 fn test_lifecycle_on_enter_abort(cx: &mut TestAppContext) {
205 let lifecycle = TestLifecycle {
206 should_abort: true,
207 should_redirect: false,
208 };
209 let request = NavigationRequest::new("/test".to_string());
210
211 let result = cx.update(|cx| pollster::block_on(lifecycle.on_enter(cx, &request)));
212
213 assert!(result.is_abort());
214 }
215
216 #[gpui::test]
217 fn test_lifecycle_on_enter_redirect(cx: &mut TestAppContext) {
218 let lifecycle = TestLifecycle {
219 should_abort: false,
220 should_redirect: true,
221 };
222 let request = NavigationRequest::new("/test".to_string());
223
224 let result = cx.update(|cx| pollster::block_on(lifecycle.on_enter(cx, &request)));
225
226 assert!(result.is_redirect());
227 }
228
229 #[gpui::test]
230 fn test_lifecycle_can_deactivate_allow(cx: &mut TestAppContext) {
231 let lifecycle = TestLifecycle {
232 should_abort: false,
233 should_redirect: false,
234 };
235
236 let result = cx.update(|cx| pollster::block_on(lifecycle.can_deactivate(cx)));
237
238 assert_eq!(result, LifecycleResult::Continue);
239 }
240
241 #[gpui::test]
242 fn test_lifecycle_can_deactivate_block(cx: &mut TestAppContext) {
243 let lifecycle = TestLifecycle {
244 should_abort: true,
245 should_redirect: false,
246 };
247
248 let result = cx.update(|cx| pollster::block_on(lifecycle.can_deactivate(cx)));
249
250 assert!(result.is_abort());
251 }
252}