Skip to main content

fission_command_ui/
lib.rs

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