Skip to main content

fission_command_ui/
lib.rs

1mod actions;
2mod app;
3mod commands;
4mod components;
5mod density;
6mod routes;
7mod screens;
8mod state;
9mod theme;
10
11use anyhow::Result;
12use fission::terminal::TerminalRunOptions;
13use std::path::PathBuf;
14use theme::UiThemeMode;
15
16pub use app::CliUiApp;
17pub use state::UiState;
18
19#[derive(Clone, Debug)]
20pub struct UiOptions {
21    pub project_dir: PathBuf,
22    pub screenshot: Option<PathBuf>,
23    pub exit_after_render: bool,
24    pub width: Option<u16>,
25    pub height: Option<u16>,
26}
27
28pub fn run_ui(options: UiOptions) -> Result<()> {
29    let state = UiState::load(options.project_dir.clone());
30    let run_options = TerminalRunOptions {
31        width: options.width,
32        height: options.height,
33        screenshot: options.screenshot,
34        exit_after_render: options.exit_after_render,
35        ..TerminalRunOptions::default()
36    };
37    fission::terminal::TerminalApp::with_state(CliUiApp, state)
38        .with_title("Fission command")
39        .with_env(|env| {
40            env.theme = fission::theme::Theme::dark();
41        })
42        .with_sync_env(|state, env| {
43            env.theme = match state.theme_mode {
44                UiThemeMode::Dark => fission::theme::Theme::dark(),
45                UiThemeMode::Light => fission::theme::Theme::default(),
46            };
47        })
48        .with_state_update(|state, runtime, env| state.poll_command_status(runtime, env))
49        .with_exit_request(|state, _runtime, _env| {
50            state.request_exit_confirmation();
51            false
52        })
53        .with_should_exit(|state, _runtime, _env| state.exit_confirmed)
54        .run_with_options(run_options)
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::routes::UiRoute;
61    use crate::state::all_targets;
62    use fission_command_core::Target;
63    use std::path::PathBuf;
64
65    #[test]
66    fn cli_ui_renders_every_route_in_terminal_shell() {
67        for route in UiRoute::ALL {
68            let mut state = UiState {
69                project_dir: PathBuf::from("."),
70                project_name: "test-app".to_string(),
71                app_id: "com.example.test".to_string(),
72                project_status: "Project loaded".to_string(),
73                targets: all_targets().to_vec(),
74                selected_target: Some(Target::Web),
75                route,
76                host: "127.0.0.1".to_string(),
77                port: "8123".to_string(),
78                theme_mode: UiThemeMode::Dark,
79                ..Default::default()
80            };
81            state.devices = vec![crate::state::UiDevice {
82                id: "chrome".to_string(),
83                name: "Chrome/Chromium".to_string(),
84                target: Target::Web,
85                kind: "browser".to_string(),
86                status: "available".to_string(),
87                detail: String::new(),
88                available: true,
89            }];
90
91            let mut app = fission::terminal::TerminalApp::with_state(CliUiApp, state)
92                .with_sync_env(|state, env| {
93                    env.theme = match state.theme_mode {
94                        UiThemeMode::Dark => fission::theme::Theme::dark(),
95                        UiThemeMode::Light => fission::theme::Theme::default(),
96                    };
97                });
98            app.render_frame(120, 40).expect("route should render");
99        }
100    }
101
102    #[test]
103    fn cli_ui_renders_confirmation_dialog() {
104        let mut state = UiState {
105            project_dir: PathBuf::from("."),
106            project_name: "test-app".to_string(),
107            app_id: "com.example.test".to_string(),
108            project_status: "Project loaded".to_string(),
109            targets: all_targets().to_vec(),
110            selected_target: Some(Target::Web),
111            host: "127.0.0.1".to_string(),
112            port: "8123".to_string(),
113            theme_mode: UiThemeMode::Dark,
114            ..Default::default()
115        };
116        state.request_command_confirmation(crate::commands::UiCommand::RunSelected);
117
118        let mut app = fission::terminal::TerminalApp::with_state(CliUiApp, state).with_sync_env(
119            |state, env| {
120                env.theme = match state.theme_mode {
121                    UiThemeMode::Dark => fission::theme::Theme::dark(),
122                    UiThemeMode::Light => fission::theme::Theme::default(),
123                };
124            },
125        );
126        let frame = app.render_frame(120, 40).expect("dialog should render");
127        assert!(frame
128            .as_plain_text()
129            .contains("Confirm: run the selected target"));
130        assert!(frame.as_plain_text().contains("Cancel"));
131    }
132}