Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/table/mod_tests.rs
StratoSDK / crates / strato-ui-core / src / elements / table / mod_tests.rs
1use super::*;
2 
3fn create_empty_element() -> Box<dyn Element> {
4 Box::new(super::super::Empty::new())
5}
6 
7fn create_header(width: TableColumnWidth) -> TableHeader {
8 TableHeader::new(create_empty_element()).with_width(width)
9}
10 
11fn create_test_state() -> TableStateHandle {
12 TableStateHandle::new(0, |_, _| vec![])
13}
14 
15// ============================================================================
16// Construction Validation Tests
17// ============================================================================
18 
19#[test]
20fn test_table_construction_with_row_render_fn() {
21 let state = TableStateHandle::new(3, |_, _| {
22 vec![
23 create_empty_element(),
24 create_empty_element(),
25 create_empty_element(),
26 ]
27 });
28 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
29 create_header(TableColumnWidth::Fixed(100.0)),
30 create_header(TableColumnWidth::Fixed(100.0)),
31 create_header(TableColumnWidth::Fixed(100.0)),
32 ]);
33 
34 assert_eq!(table.total_row_count(), 3);
35 assert_eq!(table.column_count(), 3);
36}
37 
38// ============================================================================
39// Column Width Computation Tests
40// ============================================================================
41 
42#[test]
43fn test_compute_column_widths_fixed_only() {
44 let state = create_test_state();
45 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
46 create_header(TableColumnWidth::Fixed(100.0)),
47 create_header(TableColumnWidth::Fixed(150.0)),
48 create_header(TableColumnWidth::Fixed(50.0)),
49 ]);
50 
51 let widths = table.compute_column_widths(500.0, &[0.0, 0.0, 0.0]);
52 assert_eq!(widths, vec![100.0, 150.0, 50.0]);
53}
54 
55#[test]
56fn test_compute_column_widths_flex_only() {
57 let state = create_test_state();
58 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
59 create_header(TableColumnWidth::Flex(1.0)),
60 create_header(TableColumnWidth::Flex(2.0)),
61 create_header(TableColumnWidth::Flex(1.0)),
62 ]);
63 
64 let widths = table.compute_column_widths(400.0, &[0.0, 0.0, 0.0]);
65 assert_eq!(widths, vec![100.0, 200.0, 100.0]);
66}
67 
68#[test]
69fn test_compute_column_widths_fraction() {
70 let state = create_test_state();
71 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
72 create_header(TableColumnWidth::Fraction(0.25)),
73 create_header(TableColumnWidth::Fraction(0.5)),
74 create_header(TableColumnWidth::Fraction(0.25)),
75 ]);
76 
77 let widths = table.compute_column_widths(400.0, &[0.0, 0.0, 0.0]);
78 assert_eq!(widths, vec![100.0, 200.0, 100.0]);
79}
80 
81#[test]
82fn test_compute_column_widths_mixed() {
83 let state = create_test_state();
84 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
85 create_header(TableColumnWidth::Fixed(100.0)),
86 create_header(TableColumnWidth::Flex(1.0)),
87 create_header(TableColumnWidth::Fraction(0.2)),
88 ]);
89 
90 let widths = table.compute_column_widths(500.0, &[0.0, 0.0, 0.0]);
91 assert_eq!(widths[0], 100.0);
92 assert_eq!(widths[2], 100.0);
93 assert_eq!(widths[1], 300.0);
94}
95 
96#[test]
97fn test_compute_column_widths_intrinsic_scaling() {
98 let state = create_test_state();
99 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
100 create_header(TableColumnWidth::Intrinsic),
101 create_header(TableColumnWidth::Intrinsic),
102 ]);
103 
104 let widths = table.compute_column_widths(100.0, &[150.0, 150.0]);
105 assert_eq!(widths[0], 50.0);
106 assert_eq!(widths[1], 50.0);
107}
108 
109#[test]
110fn test_compute_column_widths_intrinsic_no_scaling_needed() {
111 let state = create_test_state();
112 let table = Table::new(state, 800.0, 500.0).with_headers(vec![
113 create_header(TableColumnWidth::Intrinsic),
114 create_header(TableColumnWidth::Intrinsic),
115 ]);
116 
117 let widths = table.compute_column_widths(400.0, &[100.0, 100.0]);
118 assert_eq!(widths[0], 100.0);
119 assert_eq!(widths[1], 100.0);
120}
121 
122#[test]
123fn test_compute_column_widths_empty_table() {
124 let state = create_test_state();
125 let table = Table::new(state, 800.0, 500.0);
126 
127 let widths = table.compute_column_widths(500.0, &[]);
128 assert!(widths.is_empty());
129}
130 
131// ============================================================================
132// TableStateHandle Tests
133// ============================================================================
134 
135#[test]
136fn test_table_state_handle_new() {
137 let state = create_test_state();
138 assert!(state.column_widths().is_empty());
139}
140 
141#[test]
142fn test_table_state_handle_shared_across_clones() {
143 let state = create_test_state();
144 let state_clone = state.clone();
145 
146 {
147 let mut inner = state.inner.borrow_mut();
148 inner.column_widths = vec![100.0, 200.0];
149 }
150 
151 assert_eq!(state_clone.column_widths(), vec![100.0, 200.0]);
152}
153 
154#[test]
155fn test_table_state_handle_row_count() {
156 let state = TableStateHandle::new(100, |_, _| vec![]);
157 assert_eq!(state.row_count(), 100);
158 
159 state.set_row_count(200);
160 assert_eq!(state.row_count(), 200);
161}
162 
163// ============================================================================
164// Helper Function Tests
165// ============================================================================
166 
167#[test]
168fn test_compute_column_lefts() {
169 let widths = vec![100.0, 150.0, 50.0];
170 let lefts = Table::compute_column_lefts(&widths);
171 assert_eq!(lefts, vec![0.0, 100.0, 250.0]);
172}
173 
174#[test]
175fn test_compute_column_lefts_empty() {
176 let widths: Vec<f32> = vec![];
177 let lefts = Table::compute_column_lefts(&widths);
178 assert!(lefts.is_empty());
179}
180 
181#[test]
182fn test_compute_column_lefts_single() {
183 let widths = vec![100.0];
184 let lefts = Table::compute_column_lefts(&widths);
185 assert_eq!(lefts, vec![0.0]);
186}
187 
188// ============================================================================
189// TableConfig Tests
190// ============================================================================
191 
192#[test]
193fn test_table_config_default() {
194 let config = TableConfig::default();
195 assert_eq!(config.border_width, 1.0);
196 assert_eq!(config.cell_padding, 8.0);
197 assert!(config.row_background.alternating.is_none());
198 assert_eq!(config.vertical_sizing, TableVerticalSizing::Viewported);
199}
200 
201#[test]
202fn test_table_column_width_default() {
203 let width = TableColumnWidth::default();
204 assert!(matches!(width, TableColumnWidth::Flex(1.0)));
205}
206 
207// ============================================================================
208// Virtualization Tests
209// ============================================================================
210 
211#[test]
212fn test_sumtree_initialization_with_row_count() {
213 let state = TableStateHandle::new(100, |_, _| vec![]);
214 state.set_row_count(100);
215 
216 let inner = state.inner.borrow();
217 assert_eq!(inner.row_count, 100);
218}
219 
220#[test]
221fn test_sumtree_row_height_invalidation() {
222 let state = TableStateHandle::new(10, |_, _| vec![]);
223 
224 {
225 let mut inner = state.inner.borrow_mut();
226 let mut tree = SumTree::new();
227 for _ in 0..10 {
228 tree.push(TableRowItem {
229 height: Some(Pixels::new(50.0)),
230 });
231 }
232 inner.rows = tree;
233 inner.last_measured_row_index = 9;
234 }
235 
236 state.invalidate_row_height(5);
237 
238 let inner = state.inner.borrow();
239 let mut cursor = inner.rows.cursor::<RowCount, ()>();
240 cursor.seek(&RowCount(5), crate::linear_index::SeekBias::Right);
241 if let Some(item) = cursor.item() {
242 assert!(item.height.is_none());
243 }
244 assert!(inner.last_measured_row_index <= 4);
245}
246 
247#[test]
248fn test_scroll_to_row_updates_scroll_offset() {
249 let state = TableStateHandle::new(100, |_, _| vec![]);
250 
251 state.scroll_to_row(50, None);
252 
253 let inner = state.inner.borrow();
254 assert_eq!(inner.scroll_top.row_index.0, 50);
255 assert_eq!(inner.scroll_top.offset_from_start, Pixels::zero());
256}
257 
258#[test]
259fn test_scroll_to_row_with_offset() {
260 let state = TableStateHandle::new(100, |_, _| vec![]);
261 
262 state.scroll_to_row(25, Some(Pixels::new(10.0)));
263 
264 let inner = state.inner.borrow();
265 assert_eq!(inner.scroll_top.row_index.0, 25);
266 assert_eq!(inner.scroll_top.offset_from_start, Pixels::new(10.0));
267}
268 
269#[test]
270fn test_approximate_height_with_measured_rows() {
271 let state = TableStateHandle::new(10, |_, _| vec![]);
272 
273 {
274 let mut inner = state.inner.borrow_mut();
275 inner.header_height = Pixels::new(40.0);
276 let mut tree = SumTree::new();
277 for _ in 0..10 {
278 tree.push(TableRowItem {
279 height: Some(Pixels::new(30.0)),
280 });
281 }
282 inner.rows = tree;
283 }
284 
285 let inner = state.inner.borrow();
286 let approx_height = inner.approximate_height();
287 assert_eq!(approx_height, Pixels::new(340.0));
288}
289 
290#[test]
291fn test_approximate_height_estimates_unmeasured_rows() {
292 let state = TableStateHandle::new(10, |_, _| vec![]);
293 
294 {
295 let mut inner = state.inner.borrow_mut();
296 inner.header_height = Pixels::new(40.0);
297 let mut tree = SumTree::new();
298 for i in 0..10 {
299 tree.push(TableRowItem {
300 height: if i < 5 { Some(Pixels::new(30.0)) } else { None },
301 });
302 }
303 inner.rows = tree;
304 }
305 
306 let inner = state.inner.borrow();
307 let approx_height = inner.approximate_height();
308 assert_eq!(approx_height, Pixels::new(340.0));
309}
310 
311#[test]
312fn test_visible_start_row_idx_tracking() {
313 let state = TableStateHandle::new(100, |_, _| vec![]);
314 
315 {
316 let mut inner = state.inner.borrow_mut();
317 inner.visible_start_row_idx = 42;
318 }
319 
320 let inner = state.inner.borrow();
321 assert_eq!(inner.visible_start_row_idx, 42);
322}
323 
324#[test]
325fn test_max_scroll_offset_respects_viewport() {
326 let state = TableStateHandle::new(10, |_, _| vec![]);
327 
328 {
329 let mut inner = state.inner.borrow_mut();
330 inner.header_height = Pixels::new(40.0);
331 inner.viewport_height = Pixels::new(200.0);
332 let mut tree = SumTree::new();
333 for _ in 0..10 {
334 tree.push(TableRowItem {
335 height: Some(Pixels::new(50.0)),
336 });
337 }
338 inner.rows = tree;
339 }
340 
341 let inner = state.inner.borrow();
342 let max_scroll_px = inner.header_height + inner.rows.summary().height - inner.viewport_height;
343 assert!(max_scroll_px > Pixels::zero());
344}
345 
346// ============================================================================
347// Multiple Layout Tests
348// ============================================================================
349 
350#[test]
351fn test_visible_row_count_starts_at_zero() {
352 let state = TableStateHandle::new(5, |_, _| {
353 vec![create_empty_element(), create_empty_element()]
354 });
355 let table = Table::new(state, 400.0, 300.0).with_headers(vec![
356 create_header(TableColumnWidth::Fixed(100.0)),
357 create_header(TableColumnWidth::Fixed(100.0)),
358 ]);
359 
360 assert_eq!(table.visible_row_count(), 0);
361}
362 
363#[test]
364fn test_children_vector_is_initially_empty() {
365 let state = TableStateHandle::new(10, |_, _| {
366 vec![create_empty_element(), create_empty_element()]
367 });
368 let table = Table::new(state, 400.0, 300.0).with_headers(vec![
369 create_header(TableColumnWidth::Fixed(100.0)),
370 create_header(TableColumnWidth::Fixed(100.0)),
371 ]);
372 
373 assert_eq!(table.visible_row_count(), 0);
374}
375