1use super::table_row::TableRow;
2use crate::{RedistributableColumnsState, TableResizeBehavior};
3
4fn is_almost_eq(a: &[f32], b: &[f32]) -> bool {
5 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
6}
7
8fn cols_to_str(cols: &[f32], total_size: f32) -> String {
9 cols.iter()
10 .map(|f| "*".repeat(f32::round(f * total_size) as usize))
11 .collect::<Vec<String>>()
12 .join("|")
13}
14
15fn parse_resize_behavior(
16 input: &str,
17 total_size: f32,
18 expected_cols: usize,
19) -> Vec<TableResizeBehavior> {
20 let mut resize_behavior = Vec::with_capacity(expected_cols);
21 for col in input.split('|') {
22 if col.starts_with('X') || col.is_empty() {
23 resize_behavior.push(TableResizeBehavior::None);
24 } else if col.starts_with('*') {
25 resize_behavior.push(TableResizeBehavior::MinSize(col.len() as f32 / total_size));
26 } else {
27 panic!("invalid test input: unrecognized resize behavior: {}", col);
28 }
29 }
30
31 if resize_behavior.len() != expected_cols {
32 panic!(
33 "invalid test input: expected {} columns, got {}",
34 expected_cols,
35 resize_behavior.len()
36 );
37 }
38 resize_behavior
39}
40
41mod reset_column_size {
42 use super::*;
43
44 fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
45 let mut widths = Vec::new();
46 let mut column_index = None;
47 for (index, col) in input.split('|').enumerate() {
48 widths.push(col.len() as f32);
49 if col.starts_with('X') {
50 column_index = Some(index);
51 }
52 }
53
54 for w in &widths {
55 assert!(w.is_finite(), "incorrect number of columns");
56 }
57 let total = widths.iter().sum::<f32>();
58 for width in &mut widths {
59 *width /= total;
60 }
61 (widths, total, column_index)
62 }
63
64 #[track_caller]
65 fn check_reset_size(initial_sizes: &str, widths: &str, expected: &str, resize_behavior: &str) {
66 let (initial_sizes, total_1, None) = parse(initial_sizes) else {
67 panic!("invalid test input: initial sizes should not be marked");
68 };
69 let (widths, total_2, Some(column_index)) = parse(widths) else {
70 panic!("invalid test input: widths should be marked");
71 };
72 assert_eq!(
73 total_1, total_2,
74 "invalid test input: total width not the same {total_1}, {total_2}"
75 );
76 let (expected, total_3, None) = parse(expected) else {
77 panic!("invalid test input: expected should not be marked: {expected:?}");
78 };
79 assert_eq!(
80 total_2, total_3,
81 "invalid test input: total width not the same"
82 );
83 let cols = initial_sizes.len();
84 let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
85 let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
86 let result = RedistributableColumnsState::reset_to_initial_size(
87 column_index,
88 TableRow::from_vec(widths, cols),
89 TableRow::from_vec(initial_sizes, cols),
90 &resize_behavior,
91 );
92 let result_slice = result.as_slice();
93 let is_eq = is_almost_eq(result_slice, &expected);
94 if !is_eq {
95 let result_str = cols_to_str(result_slice, total_1);
96 let expected_str = cols_to_str(&expected, total_1);
97 panic!(
98 "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_slice:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
99 );
100 }
101 }
102
103 macro_rules! check_reset_size {
104 (columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
105 check_reset_size($initial, $current, $expected, $resizing);
106 };
107 ($name:ident, columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
108 #[test]
109 fn $name() {
110 check_reset_size($initial, $current, $expected, $resizing);
111 }
112 };
113 }
114
115 check_reset_size!(
116 basic_right,
117 columns: 5,
118 starting: "**|**|**|**|**",
119 snapshot: "**|**|X|***|**",
120 expected: "**|**|**|**|**",
121 minimums: "X|*|*|*|*",
122 );
123
124 check_reset_size!(
125 basic_left,
126 columns: 5,
127 starting: "**|**|**|**|**",
128 snapshot: "**|**|***|X|**",
129 expected: "**|**|**|**|**",
130 minimums: "X|*|*|*|**",
131 );
132
133 check_reset_size!(
134 squashed_left_reset_col2,
135 columns: 6,
136 starting: "*|***|**|**|****|*",
137 snapshot: "*|*|X|*|*|********",
138 expected: "*|*|**|*|*|*******",
139 minimums: "X|*|*|*|*|*",
140 );
141
142 check_reset_size!(
143 grow_cascading_right,
144 columns: 6,
145 starting: "*|***|****|**|***|*",
146 snapshot: "*|***|X|**|**|*****",
147 expected: "*|***|****|*|*|****",
148 minimums: "X|*|*|*|*|*",
149 );
150
151 check_reset_size!(
152 squashed_right_reset_col4,
153 columns: 6,
154 starting: "*|***|**|**|****|*",
155 snapshot: "*|********|*|*|X|*",
156 expected: "*|*****|*|*|****|*",
157 minimums: "X|*|*|*|*|*",
158 );
159
160 check_reset_size!(
161 reset_col6_right,
162 columns: 6,
163 starting: "*|***|**|***|***|**",
164 snapshot: "*|***|**|***|**|XXX",
165 expected: "*|***|**|***|***|**",
166 minimums: "X|*|*|*|*|*",
167 );
168
169 check_reset_size!(
170 reset_col6_left,
171 columns: 6,
172 starting: "*|***|**|***|***|**",
173 snapshot: "*|***|**|***|****|X",
174 expected: "*|***|**|***|***|**",
175 minimums: "X|*|*|*|*|*",
176 );
177
178 check_reset_size!(
179 last_column_grow_cascading,
180 columns: 6,
181 starting: "*|***|**|**|**|***",
182 snapshot: "*|*******|*|**|*|X",
183 expected: "*|******|*|*|*|***",
184 minimums: "X|*|*|*|*|*",
185 );
186
187 check_reset_size!(
188 goes_left_when_left_has_extreme_diff,
189 columns: 6,
190 starting: "*|***|****|**|**|***",
191 snapshot: "*|********|X|*|**|**",
192 expected: "*|*****|****|*|**|**",
193 minimums: "X|*|*|*|*|*",
194 );
195
196 check_reset_size!(
197 basic_shrink_right,
198 columns: 6,
199 starting: "**|**|**|**|**|**",
200 snapshot: "**|**|XXX|*|**|**",
201 expected: "**|**|**|**|**|**",
202 minimums: "X|*|*|*|*|*",
203 );
204
205 check_reset_size!(
206 shrink_should_go_left,
207 columns: 6,
208 starting: "*|***|**|*|*|*",
209 snapshot: "*|*|XXX|**|*|*",
210 expected: "*|**|**|**|*|*",
211 minimums: "X|*|*|*|*|*",
212 );
213
214 check_reset_size!(
215 shrink_should_go_right,
216 columns: 6,
217 starting: "*|***|**|**|**|*",
218 snapshot: "*|****|XXX|*|*|*",
219 expected: "*|****|**|**|*|*",
220 minimums: "X|*|*|*|*|*",
221 );
222}
223
224mod drag_handle {
225 use super::*;
226
227 fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
228 let mut widths = Vec::new();
229 let column_index = input.replace("*", "").find("I");
230 for col in input.replace("I", "|").split('|') {
231 widths.push(col.len() as f32);
232 }
233
234 for w in &widths {
235 assert!(w.is_finite(), "incorrect number of columns");
236 }
237 let total = widths.iter().sum::<f32>();
238 for width in &mut widths {
239 *width /= total;
240 }
241 (widths, total, column_index)
242 }
243
244 #[track_caller]
245 fn check(distance: i32, widths: &str, expected: &str, resize_behavior: &str) {
246 let (widths, total_1, Some(column_index)) = parse(widths) else {
247 panic!("invalid test input: widths should be marked");
248 };
249 let (expected, total_2, None) = parse(expected) else {
250 panic!("invalid test input: expected should not be marked: {expected:?}");
251 };
252 assert_eq!(
253 total_1, total_2,
254 "invalid test input: total width not the same"
255 );
256 let cols = widths.len();
257 let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
258 let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
259
260 let distance = distance as f32 / total_1;
261
262 let mut widths_table_row = TableRow::from_vec(widths, cols);
263 RedistributableColumnsState::drag_column_handle(
264 distance,
265 column_index,
266 &mut widths_table_row,
267 &resize_behavior,
268 );
269
270 let result_widths = widths_table_row.as_slice();
271 let is_eq = is_almost_eq(result_widths, &expected);
272 if !is_eq {
273 let result_str = cols_to_str(result_widths, total_1);
274 let expected_str = cols_to_str(&expected, total_1);
275 panic!(
276 "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_widths:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
277 );
278 }
279 }
280
281 macro_rules! check {
282 (columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
283 check($dist, $current, $expected, $resizing);
284 };
285 ($name:ident, columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
286 #[test]
287 fn $name() {
288 check($dist, $current, $expected, $resizing);
289 }
290 };
291 }
292
293 check!(
294 basic_right_drag,
295 columns: 3,
296 distance: 1,
297 snapshot: "**|**I**",
298 expected: "**|***|*",
299 minimums: "X|*|*",
300 );
301
302 check!(
303 drag_left_against_mins,
304 columns: 5,
305 distance: -1,
306 snapshot: "*|*|*|*I*******",
307 expected: "*|*|*|*|*******",
308 minimums: "X|*|*|*|*",
309 );
310
311 check!(
312 drag_left,
313 columns: 5,
314 distance: -2,
315 snapshot: "*|*|*|*****I***",
316 expected: "*|*|*|***|*****",
317 minimums: "X|*|*|*|*",
318 );
319}