1
//! Tests for SplitsWidget: row/cell nav, option caching, render lines.
2

            
3
use super::*;
4
use crate::widgets::EditMode;
5

            
6
5
fn make_opts(n: usize) -> Vec<SelectOption> {
7
5
    (0..n)
8
5
        .map(|i| SelectOption {
9
12
            id: format!("id-{i}"),
10
12
            label: format!("Label {i}"),
11
12
        })
12
5
        .collect()
13
5
}
14

            
15
#[test]
16
1
fn new_has_one_row() {
17
1
    let sw = SplitsWidget::new(EditMode::Emacs);
18
1
    assert_eq!(sw.rows().len(), 1);
19
1
    assert_eq!(sw.row_focus, 0);
20
1
    assert_eq!(sw.col_focus, 0);
21
1
}
22

            
23
#[test]
24
1
fn add_row_inserts_after_focus() {
25
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
26
1
    sw.add_row();
27
1
    assert_eq!(sw.rows().len(), 2);
28
1
    assert_eq!(sw.row_focus, 1);
29
1
}
30

            
31
#[test]
32
1
fn add_row_three_times() {
33
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
34
1
    sw.add_row();
35
1
    sw.add_row();
36
1
    sw.add_row();
37
1
    assert_eq!(sw.rows().len(), 4);
38
1
}
39

            
40
#[test]
41
1
fn remove_row_keeps_at_least_one() {
42
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
43
1
    sw.remove_row();
44
1
    assert_eq!(sw.rows().len(), 1);
45
1
}
46

            
47
#[test]
48
1
fn remove_row_with_two_rows_leaves_one() {
49
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
50
1
    sw.add_row();
51
1
    assert_eq!(sw.rows().len(), 2);
52
1
    sw.remove_row();
53
1
    assert_eq!(sw.rows().len(), 1);
54
1
}
55

            
56
#[test]
57
1
fn remove_row_adjusts_focus_when_at_last() {
58
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
59
1
    sw.add_row();
60
1
    sw.add_row();
61
1
    assert_eq!(sw.row_focus, 2);
62
1
    sw.remove_row();
63
1
    assert_eq!(sw.row_focus, 1);
64
1
}
65

            
66
#[test]
67
1
fn advance_cell_walks_columns_then_stops_at_last_cell() {
68
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
69
5
    for _ in 0..5 {
70
5
        sw.advance_cell();
71
5
    }
72
1
    assert_eq!(
73
        sw.col_focus, 5,
74
        "single row: advance reaches the last column"
75
    );
76
1
    assert!(sw.at_last_cell());
77
1
    sw.advance_cell(); // no-op at the boundary (caller yields)
78
1
    assert_eq!(sw.col_focus, 5);
79
1
    assert_eq!(sw.row_focus, 0);
80
1
}
81

            
82
#[test]
83
1
fn advance_cell_crosses_into_next_row() {
84
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
85
1
    sw.add_row();
86
1
    sw.row_focus = 0;
87
1
    sw.col_focus = 5;
88
1
    sw.advance_cell();
89
1
    assert_eq!(sw.row_focus, 1, "advance from last col steps to next row");
90
1
    assert_eq!(sw.col_focus, 0);
91
1
}
92

            
93
#[test]
94
1
fn retreat_cell_stops_at_first_cell() {
95
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
96
1
    assert!(sw.at_first_cell());
97
1
    sw.retreat_cell(); // no-op at the boundary (caller yields)
98
1
    assert_eq!(sw.col_focus, 0);
99
1
    assert_eq!(sw.row_focus, 0);
100
1
}
101

            
102
#[test]
103
1
fn retreat_cell_crosses_into_previous_row() {
104
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
105
1
    sw.add_row();
106
    // focus is now (row 1, col 0); retreat → (row 0, last col)
107
1
    sw.retreat_cell();
108
1
    assert_eq!(sw.row_focus, 0);
109
1
    assert_eq!(sw.col_focus, 5);
110
1
}
111

            
112
#[test]
113
1
fn add_row_inherits_cached_options() {
114
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
115
1
    sw.set_account_options(make_opts(3));
116
1
    sw.set_commodity_options(make_opts(2));
117
1
    sw.add_row();
118
1
    let row = &sw.rows()[1];
119
1
    assert_eq!(row.from.option_count(), 3, "new row from-account seeded");
120
1
    assert_eq!(row.to.option_count(), 3, "new row to-account seeded");
121
1
    assert_eq!(
122
1
        row.from_commodity.option_count(),
123
        2,
124
        "new row from-commodity seeded"
125
    );
126
1
    assert_eq!(
127
1
        row.to_commodity.option_count(),
128
        2,
129
        "new row to-commodity seeded"
130
    );
131
1
}
132

            
133
#[test]
134
1
fn render_lines_shows_selected_labels_and_focus_marker() {
135
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
136
1
    sw.set_account_options(make_opts(2));
137
1
    let lines = sw.render_lines();
138
1
    assert_eq!(lines.len(), 1);
139
1
    assert!(
140
1
        lines[0].starts_with('▸'),
141
        "focused row marked: {}",
142
        lines[0]
143
    );
144
1
    assert!(
145
1
        lines[0].contains("Label 0"),
146
        "selected label shown: {}",
147
        lines[0]
148
    );
149
1
    assert!(
150
1
        lines[0].contains("[Label 0]"),
151
        "focused cell bracketed: {}",
152
        lines[0]
153
    );
154
1
}
155

            
156
#[test]
157
1
fn next_row_wraps_single() {
158
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
159
1
    sw.next_row();
160
1
    assert_eq!(sw.row_focus, 0);
161
1
}
162

            
163
#[test]
164
1
fn next_row_wraps_two() {
165
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
166
1
    sw.add_row();
167
1
    sw.row_focus = 0;
168
1
    sw.next_row();
169
1
    assert_eq!(sw.row_focus, 1);
170
1
    sw.next_row();
171
1
    assert_eq!(sw.row_focus, 0);
172
1
}
173

            
174
#[test]
175
1
fn prev_row_wraps() {
176
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
177
1
    sw.add_row();
178
1
    sw.row_focus = 0;
179
1
    sw.prev_row();
180
1
    assert_eq!(sw.row_focus, 1);
181
1
}
182

            
183
#[test]
184
1
fn col_kind_select_for_first_four() {
185
1
    assert_eq!(SplitRow::col_kind(COL_FROM), WidgetKind::Select);
186
1
    assert_eq!(SplitRow::col_kind(COL_TO), WidgetKind::Select);
187
1
    assert_eq!(SplitRow::col_kind(COL_FROM_COMM), WidgetKind::Select);
188
1
    assert_eq!(SplitRow::col_kind(COL_TO_COMM), WidgetKind::Select);
189
1
}
190

            
191
#[test]
192
1
fn col_kind_amount_for_last_two() {
193
1
    assert_eq!(SplitRow::col_kind(COL_VALUE), WidgetKind::Amount);
194
1
    assert_eq!(SplitRow::col_kind(COL_TO_AMOUNT), WidgetKind::Amount);
195
1
}
196

            
197
#[test]
198
1
fn set_account_options_populates_all_rows() {
199
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
200
1
    sw.add_row();
201
1
    sw.set_account_options(make_opts(3));
202
2
    for row in sw.rows() {
203
2
        assert_eq!(row.from.option_count(), 3);
204
2
        assert_eq!(row.to.option_count(), 3);
205
    }
206
1
}
207

            
208
#[test]
209
1
fn set_commodity_options_populates_all_rows() {
210
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
211
1
    sw.add_row();
212
1
    sw.set_commodity_options(make_opts(2));
213
2
    for row in sw.rows() {
214
2
        assert_eq!(row.from_commodity.option_count(), 2);
215
2
        assert_eq!(row.to_commodity.option_count(), 2);
216
    }
217
1
}
218

            
219
#[test]
220
1
fn focused_subwidget_kind_follows_col_focus() {
221
1
    let mut sw = SplitsWidget::new(EditMode::Emacs);
222
1
    sw.col_focus = COL_FROM;
223
1
    assert_eq!(sw.focused_subwidget_kind(), WidgetKind::Select);
224
1
    sw.col_focus = COL_VALUE;
225
1
    assert_eq!(sw.focused_subwidget_kind(), WidgetKind::Amount);
226
1
}
227

            
228
#[test]
229
1
fn display_shows_row_and_col() {
230
1
    let sw = SplitsWidget::new(EditMode::Emacs);
231
1
    let d = sw.display();
232
1
    assert!(d.contains("1 split(s)"), "row count: {d}");
233
1
    assert!(d.contains("row 0"), "row focus: {d}");
234
1
}