1
//! Unit tests for the view module.
2

            
3
use super::*;
4
use crate::pane::PaneId;
5
use crate::tabs::config::ConfigTab;
6
use crate::tabs::list::ListTab;
7
use crate::tabs::nms::ConsoleState;
8
use crate::tabs::reports::ReportsTab;
9
use crate::widgets::EditMode;
10
use cli_core::render::schema;
11

            
12
6
fn make_ctx() -> DrawCtx {
13
6
    DrawCtx {
14
6
        edit_mode: EditMode::Emacs,
15
6
        is_active: false,
16
6
    }
17
6
}
18

            
19
#[test]
20
1
fn view_id_all_has_six_entries() {
21
1
    assert_eq!(ViewId::ALL.len(), 6);
22
1
}
23

            
24
#[test]
25
1
fn view_id_index_matches_discriminant() {
26
1
    assert_eq!(ViewId::Accounts.index(), 0);
27
1
    assert_eq!(ViewId::Transactions.index(), 1);
28
1
    assert_eq!(ViewId::Commodities.index(), 2);
29
1
    assert_eq!(ViewId::Reports.index(), 3);
30
1
    assert_eq!(ViewId::Config.index(), 4);
31
1
    assert_eq!(ViewId::Console.index(), 5);
32
1
}
33

            
34
#[test]
35
1
fn view_id_all_round_trips_through_index() {
36
6
    for (i, id) in ViewId::ALL.iter().enumerate() {
37
6
        assert_eq!(id.index(), i);
38
    }
39
1
}
40

            
41
#[test]
42
1
fn view_id_labels_are_non_empty() {
43
6
    for id in ViewId::ALL {
44
6
        assert!(!id.label().is_empty());
45
    }
46
1
}
47

            
48
#[test]
49
1
fn tab_alias_matches_view_id() {
50
1
    assert_eq!(Tab::Accounts, ViewId::Accounts);
51
1
    assert_eq!(Tab::Console, ViewId::Console);
52
1
}
53

            
54
#[test]
55
1
fn view_ref_list_id_and_title() {
56
1
    let tab = ListTab::new("(list-accounts)", &schema::ACCOUNTS);
57
1
    let vr = ViewRef::List(ViewId::Accounts, &tab);
58
1
    assert_eq!(vr.id(), ViewId::Accounts);
59
1
    assert_eq!(vr.title(), "Accounts");
60
1
}
61

            
62
#[test]
63
1
fn view_ref_reports_id_and_title() {
64
1
    let tab = ReportsTab::new();
65
1
    let vr = ViewRef::Reports(&tab);
66
1
    assert_eq!(vr.id(), ViewId::Reports);
67
1
    assert_eq!(vr.title(), "Reports");
68
1
}
69

            
70
#[test]
71
1
fn view_ref_config_id_and_title() {
72
1
    let tab = ConfigTab::new();
73
1
    let vr = ViewRef::Config(&tab);
74
1
    assert_eq!(vr.id(), ViewId::Config);
75
1
    assert_eq!(vr.title(), "Config");
76
1
}
77

            
78
#[test]
79
1
fn view_ref_console_id_and_title() {
80
1
    let tab = ConsoleState::new();
81
1
    let vr = ViewRef::Console(&tab);
82
1
    assert_eq!(vr.id(), ViewId::Console);
83
1
    assert_eq!(vr.title(), "Console");
84
1
}
85

            
86
#[test]
87
1
fn list_handle_select_next_consumed() {
88
1
    let mut tab = ListTab::new("(list-accounts)", &schema::ACCOUNTS);
89
1
    let ctx = make_ctx();
90
1
    assert!(matches!(
91
1
        tab.handle(crate::event::Intent::ListSelectNext, &ctx),
92
        Handled::Consumed(_)
93
    ));
94
1
}
95

            
96
#[test]
97
1
fn list_handle_unknown_intent_ignored() {
98
1
    let mut tab = ListTab::new("(list-accounts)", &schema::ACCOUNTS);
99
1
    let ctx = make_ctx();
100
1
    assert!(matches!(
101
1
        tab.handle(crate::event::Intent::Quit, &ctx),
102
        Handled::Ignored
103
    ));
104
1
}
105

            
106
#[test]
107
1
fn reports_handle_always_ignored() {
108
1
    let mut tab = ReportsTab::new();
109
1
    let ctx = make_ctx();
110
1
    assert!(matches!(
111
1
        tab.handle(crate::event::Intent::ListSelectNext, &ctx),
112
        Handled::Ignored
113
    ));
114
1
}
115

            
116
#[test]
117
1
fn console_handle_always_ignored() {
118
1
    let mut tab = ConsoleState::new();
119
1
    let ctx = make_ctx();
120
1
    assert!(matches!(
121
1
        tab.handle(crate::event::Intent::ListSelectNext, &ctx),
122
        Handled::Ignored
123
    ));
124
1
}
125

            
126
#[test]
127
1
fn view_mut_list_handle_consumed() {
128
1
    let mut tab = ListTab::new("(list-accounts)", &schema::ACCOUNTS);
129
1
    let ctx = make_ctx();
130
1
    let mut vm = ViewMut::List(ViewId::Accounts, &mut tab);
131
1
    assert!(matches!(
132
1
        vm.handle(crate::event::Intent::ListSelectNext, &ctx),
133
        Handled::Consumed(_)
134
    ));
135
1
}
136

            
137
#[test]
138
1
fn view_mut_config_handle_select_consumed() {
139
1
    let mut tab = ConfigTab::new();
140
1
    let ctx = make_ctx();
141
1
    let mut vm = ViewMut::Config(&mut tab);
142
1
    assert!(matches!(
143
1
        vm.handle(crate::event::Intent::ListSelectNext, &ctx),
144
        Handled::Consumed(_)
145
    ));
146
1
}
147

            
148
#[test]
149
1
fn draw_console_scroll_zero_shows_newest_line() {
150
    use ratatui::Terminal;
151
    use ratatui::backend::TestBackend;
152
    use ratatui::layout::Rect;
153

            
154
1
    let mut state = ConsoleState::new();
155
20
    for i in 0..20 {
156
20
        state.push_scrollback(format!("line{i:02}"));
157
20
    }
158
1
    let ctx = DrawCtx {
159
1
        edit_mode: EditMode::Emacs,
160
1
        is_active: false,
161
1
    };
162
1
    let backend = TestBackend::new(40, 12);
163
1
    let mut terminal = Terminal::new(backend).expect("terminal");
164
1
    terminal
165
1
        .draw(|f| {
166
1
            let area = Rect::new(0, 0, 40, 12);
167
1
            state.draw(f, area, &ctx);
168
1
        })
169
1
        .expect("draw");
170
1
    let buf = terminal.backend().buffer().clone();
171
1
    let rendered: String = (0..buf.area.height)
172
12
        .map(|y| {
173
12
            (0..buf.area.width)
174
480
                .map(|x| {
175
480
                    buf.cell((x, y))
176
480
                        .map_or(' ', |c| c.symbol().chars().next().unwrap_or(' '))
177
480
                })
178
12
                .collect::<String>()
179
12
        })
180
1
        .collect::<Vec<_>>()
181
1
        .join("\n");
182
1
    assert!(
183
1
        rendered.contains("line19"),
184
        "newest line missing:\n{rendered}"
185
    );
186
1
    assert!(
187
1
        !rendered.contains("line00"),
188
        "oldest line should be scrolled off:\n{rendered}"
189
    );
190
1
}
191

            
192
#[test]
193
1
fn draw_console_scroll_up_shows_older_lines() {
194
    use ratatui::Terminal;
195
    use ratatui::backend::TestBackend;
196
    use ratatui::layout::Rect;
197

            
198
1
    let mut state = ConsoleState::new();
199
20
    for i in 0..20 {
200
20
        state.push_scrollback(format!("line{i:02}"));
201
20
    }
202
1
    state.scroll_up(10);
203
1
    let ctx = DrawCtx {
204
1
        edit_mode: EditMode::Emacs,
205
1
        is_active: false,
206
1
    };
207
1
    let backend = TestBackend::new(40, 12);
208
1
    let mut terminal = Terminal::new(backend).expect("terminal");
209
1
    terminal
210
1
        .draw(|f| {
211
1
            let area = Rect::new(0, 0, 40, 12);
212
1
            state.draw(f, area, &ctx);
213
1
        })
214
1
        .expect("draw");
215
1
    let buf = terminal.backend().buffer().clone();
216
1
    let rendered: String = (0..buf.area.height)
217
12
        .map(|y| {
218
12
            (0..buf.area.width)
219
480
                .map(|x| {
220
480
                    buf.cell((x, y))
221
480
                        .map_or(' ', |c| c.symbol().chars().next().unwrap_or(' '))
222
480
                })
223
12
                .collect::<String>()
224
12
        })
225
1
        .collect::<Vec<_>>()
226
1
        .join("\n");
227
1
    assert!(
228
1
        !rendered.contains("line19"),
229
        "newest line should be scrolled off:\n{rendered}"
230
    );
231
1
}
232

            
233
#[test]
234
1
fn draw_console_scrollback_pane_focused_changes_title() {
235
    use ratatui::Terminal;
236
    use ratatui::backend::TestBackend;
237
    use ratatui::layout::Rect;
238

            
239
1
    let mut state = ConsoleState::new();
240
1
    state.panes.focus(PaneId::Scrollback);
241
1
    let ctx = DrawCtx {
242
1
        edit_mode: EditMode::Emacs,
243
1
        is_active: true,
244
1
    };
245
1
    let backend = TestBackend::new(40, 12);
246
1
    let mut terminal = Terminal::new(backend).expect("terminal");
247
1
    terminal
248
1
        .draw(|f| {
249
1
            let area = Rect::new(0, 0, 40, 12);
250
1
            state.draw(f, area, &ctx);
251
1
        })
252
1
        .expect("draw");
253
1
    let buf = terminal.backend().buffer().clone();
254
1
    let first_row: String = (0..buf.area.width)
255
40
        .map(|x| {
256
40
            buf.cell((x, 0))
257
40
                .map_or(' ', |c| c.symbol().chars().next().unwrap_or(' '))
258
40
        })
259
1
        .collect();
260
1
    assert!(
261
1
        first_row.contains('S'),
262
        "Scrollback title missing: {first_row}"
263
    );
264
1
}