1use crate::widgets::column_widths::WidthChoice;
2use crate::widgets::text_input::TextInput;
3use ratatui::widgets::TableState;
4
5#[derive(Debug, Clone)]
6pub struct SortColumn {
7 pub name: String,
8 pub sort_order: Option<usize>, pub sort_descending: bool,
12 pub display_order: usize, pub is_locked: bool, pub is_to_be_locked: bool, pub is_visible: bool, pub width: WidthChoice,
18 pub shown_width: Option<u16>,
20}
21
22pub struct SortModal {
23 pub active: bool,
24 pub filter_input: TextInput,
25 pub columns: Vec<SortColumn>,
26 pub table_state: TableState,
27 pub has_unapplied_changes: bool,
28 pub history_limit: usize,
29 pub status: Option<String>,
32 pub applied_order: Vec<String>,
36 pub applied_locked: usize,
39 pub page_rows: usize,
41}
42
43impl Default for SortModal {
44 fn default() -> Self {
45 Self {
46 active: false,
47 filter_input: TextInput::new(),
48 columns: Vec::new(),
49 table_state: TableState::default(),
50 has_unapplied_changes: false,
51 history_limit: 1000,
52 status: None,
53 applied_order: Vec::new(),
54 applied_locked: 0,
55 page_rows: 10,
56 }
57 }
58}
59
60impl SortModal {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn filtered_columns(&self) -> Vec<(usize, &SortColumn)> {
66 let filter_text = self.filter_input.value().to_lowercase();
67 let mut filtered: Vec<_> = self
68 .columns
69 .iter()
70 .enumerate()
71 .filter(|(_, c)| c.name.to_lowercase().contains(&filter_text))
72 .collect();
73 filtered.sort_by_key(|(_, c)| c.display_order);
75 filtered
76 }
77
78 pub fn get_column_order(&self) -> Vec<String> {
79 let mut cols: Vec<_> = self.columns.iter().filter(|c| c.is_visible).collect();
80 cols.sort_by_key(|c| c.display_order);
81 cols.into_iter().map(|c| c.name.clone()).collect()
82 }
83
84 pub fn get_locked_columns_count(&self) -> usize {
88 let Some(last_locked) = self
89 .columns
90 .iter()
91 .filter(|c| c.is_locked)
92 .map(|c| c.display_order)
93 .max()
94 else {
95 return 0;
96 };
97 self.columns
98 .iter()
99 .filter(|c| c.is_visible && c.display_order <= last_locked)
100 .count()
101 }
102
103 pub fn get_locked_span(&self) -> usize {
105 self.columns
106 .iter()
107 .filter(|c| c.is_locked)
108 .map(|c| c.display_order + 1)
109 .max()
110 .unwrap_or(0)
111 }
112
113 pub fn get_full_column_order(&self) -> Vec<String> {
115 let mut cols: Vec<_> = self.columns.iter().collect();
116 cols.sort_by_key(|c| c.display_order);
117 cols.into_iter().map(|c| c.name.clone()).collect()
118 }
119
120 pub fn get_sorted_columns(&self) -> Vec<String> {
121 self.sorted_columns_and_directions().0
122 }
123
124 pub fn sorted_columns_and_directions(&self) -> (Vec<String>, Vec<bool>) {
127 let mut sorted: Vec<_> = self
128 .columns
129 .iter()
130 .filter_map(|c| c.sort_order.map(|o| (o, c.name.clone(), c.sort_descending)))
131 .collect();
132 sorted.sort_by_key(|(order, _, _)| *order);
133 sorted
134 .into_iter()
135 .map(|(_, name, descending)| (name, descending))
136 .unzip()
137 }
138
139 pub fn cycle_sort(&mut self) {
142 if let Some(idx) = self.table_state.selected() {
143 let filtered = self.filtered_columns();
144 if let Some((real_idx, _)) = filtered.get(idx) {
145 let real_idx = *real_idx;
146 match (
147 self.columns[real_idx].sort_order,
148 self.columns[real_idx].sort_descending,
149 ) {
150 (None, _) => {
151 let max_order = self
152 .columns
153 .iter()
154 .filter_map(|c| c.sort_order)
155 .max()
156 .unwrap_or(0);
157 self.columns[real_idx].sort_order = Some(max_order + 1);
158 self.columns[real_idx].sort_descending = false;
159 }
160 (Some(_), false) => {
161 self.columns[real_idx].sort_descending = true;
162 }
163 (Some(_), true) => self.unsort_index(real_idx),
164 }
165 self.has_unapplied_changes = true;
166 }
167 }
168 }
169
170 pub fn cycle_sort_back(&mut self) {
173 let Some(idx) = self.table_state.selected() else {
174 return;
175 };
176 let Some(&(real_idx, _)) = self.filtered_columns().get(idx) else {
177 return;
178 };
179 let col = &self.columns[real_idx];
180 match (col.sort_order, col.sort_descending) {
181 (None, _) => {
182 let max_order = self.columns.iter().filter_map(|c| c.sort_order).max();
183 self.columns[real_idx].sort_order = Some(max_order.unwrap_or(0) + 1);
184 self.columns[real_idx].sort_descending = true;
185 }
186 (Some(_), true) => self.columns[real_idx].sort_descending = false,
187 (Some(_), false) => self.unsort_index(real_idx),
188 }
189 self.has_unapplied_changes = true;
190 }
191
192 pub fn sort_entries(&self) -> Vec<usize> {
194 let mut entries: Vec<(usize, usize)> = self
195 .columns
196 .iter()
197 .enumerate()
198 .filter_map(|(i, c)| c.sort_order.map(|o| (o, i)))
199 .collect();
200 entries.sort_unstable();
201 entries.into_iter().map(|(_, i)| i).collect()
202 }
203
204 pub fn flip_sort(&mut self, entry: usize) {
206 if let Some(&i) = self.sort_entries().get(entry) {
207 self.columns[i].sort_descending = !self.columns[i].sort_descending;
208 self.has_unapplied_changes = true;
209 }
210 }
211
212 pub fn remove_sort_entry(&mut self, entry: usize) {
214 if let Some(&i) = self.sort_entries().get(entry) {
215 self.unsort_index(i);
216 self.has_unapplied_changes = true;
217 }
218 }
219
220 pub fn move_sort_entry(&mut self, entry: usize, earlier: bool) -> usize {
223 let entries = self.sort_entries();
224 let to = if earlier {
225 entry.checked_sub(1)
226 } else {
227 Some(entry + 1).filter(|to| *to < entries.len())
228 };
229 let (Some(to), Some(&from_i)) = (to, entries.get(entry)) else {
230 return entry;
231 };
232 let to_i = entries[to];
233 let a = self.columns[from_i].sort_order;
234 self.columns[from_i].sort_order = self.columns[to_i].sort_order;
235 self.columns[to_i].sort_order = a;
236 self.has_unapplied_changes = true;
237 to
238 }
239
240 pub fn add_sort(&mut self, name: &str) -> Option<usize> {
244 let i = self.columns.iter().position(|c| c.name == name)?;
245 if self.columns[i].sort_order.is_none() {
246 let max_order = self.columns.iter().filter_map(|c| c.sort_order).max();
247 self.columns[i].sort_order = Some(max_order.unwrap_or(0) + 1);
248 self.columns[i].sort_descending = false;
249 self.has_unapplied_changes = true;
250 }
251 self.sort_entries().iter().position(|&e| e == i)
252 }
253
254 pub fn remove_sort(&mut self) {
257 if let Some(idx) = self.table_state.selected() {
258 let filtered = self.filtered_columns();
259 if let Some((real_idx, _)) = filtered.get(idx) {
260 let real_idx = *real_idx;
261 if self.columns[real_idx].sort_order.is_some() {
262 self.unsort_index(real_idx);
263 self.has_unapplied_changes = true;
264 }
265 }
266 }
267 }
268
269 fn unsort_index(&mut self, real_idx: usize) {
270 let Some(old_order) = self.columns[real_idx].sort_order else {
271 return;
272 };
273 self.columns[real_idx].sort_order = None;
274 self.columns[real_idx].sort_descending = false;
275 for col in &mut self.columns {
276 if let Some(order) = col.sort_order
277 && order > old_order
278 {
279 col.sort_order = Some(order - 1);
280 }
281 }
282 }
283
284 pub fn move_column_display_up(&mut self) {
286 if let Some(idx) = self.table_state.selected() {
287 let filtered = self.filtered_columns();
288 if let Some((real_idx, _)) = filtered.get(idx) {
289 let real_idx = *real_idx;
290 let current_display_order = self.columns[real_idx].display_order;
291 let was_locked = self.columns[real_idx].is_locked;
292 if current_display_order > 0 {
293 let mut target_col_locked = false;
295 let mut target_col_to_be_locked = false;
296 for col in &self.columns {
297 if col.display_order == current_display_order - 1 {
298 target_col_locked = col.is_locked;
299 target_col_to_be_locked = col.is_to_be_locked;
300 break;
301 }
302 }
303
304 for col in &mut self.columns {
306 if col.display_order == current_display_order - 1 {
307 col.display_order = current_display_order;
308 break;
309 }
310 }
311 let new_display_order = current_display_order - 1;
312 let was_to_be_locked = self.columns[real_idx].is_to_be_locked;
313
314 let last_locked_or_to_be_order = self
317 .columns
318 .iter()
319 .filter(|c| c.is_locked || c.is_to_be_locked)
320 .map(|c| c.display_order)
321 .max()
322 .unwrap_or(0);
323
324 self.columns[real_idx].display_order = new_display_order;
326
327 if !was_locked && !was_to_be_locked {
329 if target_col_locked || target_col_to_be_locked {
330 self.columns[real_idx].is_locked = target_col_locked;
332 self.columns[real_idx].is_to_be_locked = target_col_to_be_locked;
333 }
334 } else {
335 if (was_locked || was_to_be_locked)
338 && current_display_order == last_locked_or_to_be_order
339 {
340 for col in &mut self.columns {
345 if col.display_order > new_display_order
346 && col.display_order <= current_display_order
347 && col.is_to_be_locked
348 {
349 col.is_to_be_locked = false;
350 }
351 }
352 }
353 }
354
355 self.has_unapplied_changes = true;
356 if let Some(new_selected_idx) = self
358 .filtered_columns()
359 .iter()
360 .position(|&(idx, _)| idx == real_idx)
361 {
362 self.table_state.select(Some(new_selected_idx));
363 }
364 }
365 }
366 }
367 }
368
369 pub fn move_column_display_down(&mut self) {
371 if let Some(idx) = self.table_state.selected() {
372 let filtered = self.filtered_columns();
373 if let Some((real_idx, _)) = filtered.get(idx) {
374 let real_idx = *real_idx;
375 let max_display_order = self
376 .columns
377 .iter()
378 .map(|c| c.display_order)
379 .max()
380 .unwrap_or(0);
381 let current_display_order = self.columns[real_idx].display_order;
382 let was_locked = self.columns[real_idx].is_locked;
383 if current_display_order < max_display_order {
384 for col in &mut self.columns {
386 if col.display_order == current_display_order + 1 {
387 col.display_order = current_display_order;
388 break;
389 }
390 }
391 let new_display_order = current_display_order + 1;
392 let was_to_be_locked = self.columns[real_idx].is_to_be_locked;
393
394 self.columns[real_idx].display_order = new_display_order;
396
397 if was_locked || was_to_be_locked {
402 for (idx, col) in self.columns.iter_mut().enumerate() {
403 if idx != real_idx
406 && col.display_order >= current_display_order
407 && col.display_order < new_display_order
408 && !col.is_locked
409 {
410 col.is_to_be_locked = true;
411 }
412 }
413 }
414
415 self.has_unapplied_changes = true;
416 if let Some(new_selected_idx) = self
418 .filtered_columns()
419 .iter()
420 .position(|&(idx, _)| idx == real_idx)
421 {
422 self.table_state.select(Some(new_selected_idx));
423 }
424 }
425 }
426 }
427 }
428
429 pub fn toggle_lock_at_column(&mut self) {
431 if let Some(idx) = self.table_state.selected() {
432 let filtered = self.filtered_columns();
433 if let Some((real_idx, _)) = filtered.get(idx) {
434 let real_idx = *real_idx;
435 let target_display_order = self.columns[real_idx].display_order;
436
437 let current_locked_count = self.columns.iter().filter(|c| c.is_locked).count();
439
440 if target_display_order < current_locked_count {
442 for col in &mut self.columns {
444 col.is_locked = col.display_order < target_display_order;
445 col.is_to_be_locked = false; }
447 } else {
448 for col in &mut self.columns {
450 col.is_locked = col.display_order <= target_display_order;
451 col.is_to_be_locked = false; }
453 }
454 self.has_unapplied_changes = true;
455 }
456 }
457 }
458
459 pub fn move_selection_up(&mut self) {
460 if let Some(idx) = self.table_state.selected() {
461 let filtered = self.filtered_columns();
462 if let Some((real_idx, _)) = filtered.get(idx) {
463 let real_idx = *real_idx;
464 if let Some(current_order) = self.columns[real_idx].sort_order
465 && current_order > 1
466 {
467 for col in &mut self.columns {
468 if col.sort_order == Some(current_order - 1) {
469 col.sort_order = Some(current_order);
470 break;
471 }
472 }
473 self.columns[real_idx].sort_order = Some(current_order - 1);
474 self.has_unapplied_changes = true;
475 if let Some(new_selected_idx) = self
477 .filtered_columns()
478 .iter()
479 .position(|&(idx, _)| idx == real_idx)
480 {
481 self.table_state.select(Some(new_selected_idx));
482 }
483 }
484 }
485 }
486 }
487
488 pub fn move_selection_down(&mut self) {
489 if let Some(idx) = self.table_state.selected() {
490 let filtered = self.filtered_columns();
491 if let Some((real_idx, _)) = filtered.get(idx) {
492 let real_idx = *real_idx;
493 let max_order = self
494 .columns
495 .iter()
496 .filter_map(|c| c.sort_order)
497 .max()
498 .unwrap_or(0);
499 if let Some(current_order) = self.columns[real_idx].sort_order
500 && current_order < max_order
501 {
502 for col in &mut self.columns {
503 if col.sort_order == Some(current_order + 1) {
504 col.sort_order = Some(current_order);
505 break;
506 }
507 }
508 self.columns[real_idx].sort_order = Some(current_order + 1);
509 if let Some(new_selected_idx) = self
511 .filtered_columns()
512 .iter()
513 .position(|&(idx, _)| idx == real_idx)
514 {
515 self.table_state.select(Some(new_selected_idx));
516 }
517 self.has_unapplied_changes = true;
518 }
519 }
520 }
521 }
522
523 pub fn clear_selection(&mut self) {
524 for (idx, col) in self.columns.iter_mut().enumerate() {
526 col.sort_order = None;
527 col.sort_descending = false;
528 col.is_locked = false;
529 col.is_to_be_locked = false;
530 col.display_order = idx; col.is_visible = true; col.width = WidthChoice::Auto;
533 }
534 self.has_unapplied_changes = true;
535 }
536
537 pub fn change_width(&mut self, change: impl FnOnce(WidthChoice, Option<u16>) -> WidthChoice) {
541 let Some(idx) = self.table_state.selected() else {
542 return;
543 };
544 let Some(&(real_idx, _)) = self.filtered_columns().get(idx) else {
545 return;
546 };
547 let col = &mut self.columns[real_idx];
548 let width = change(col.width, col.shown_width);
549 if width != col.width {
550 col.width = width;
551 self.has_unapplied_changes = true;
552 }
553 }
554
555 pub fn width_choices(&self) -> Vec<(String, WidthChoice)> {
557 self.columns
558 .iter()
559 .map(|c| (c.name.clone(), c.width))
560 .collect()
561 }
562
563 pub fn toggle_visibility(&mut self) {
566 if let Some(idx) = self.table_state.selected() {
567 let filtered = self.filtered_columns();
568 if let Some(&(real_idx, _)) = filtered.get(idx) {
569 let col = &mut self.columns[real_idx];
570 col.is_visible = !col.is_visible;
571 self.has_unapplied_changes = true;
572 }
573 }
574 }
575
576 pub fn jump_selection_to_order(&mut self, new_order: usize) {
577 if let Some(idx) = self.table_state.selected() {
578 let filtered = self.filtered_columns();
579 if let Some((real_idx, _)) = filtered.get(idx) {
580 let real_idx = *real_idx;
581 let max_order = self
582 .columns
583 .iter()
584 .filter_map(|c| c.sort_order)
585 .max()
586 .unwrap_or(0);
587 let old_order = self.columns[real_idx].sort_order;
588 let last = if old_order.is_some() {
591 max_order
592 } else {
593 max_order + 1
594 };
595
596 if new_order > 0 && new_order <= last {
597 let selected_column_name = self.columns[real_idx].name.clone();
598
599 for col in &mut self.columns {
601 if col.name == selected_column_name {
602 continue; }
604 if let Some(order) = col.sort_order {
605 if let Some(old) = old_order {
606 if new_order < old && order >= new_order && order < old {
607 col.sort_order = Some(order + 1);
608 } else if new_order > old && order <= new_order && order > old {
609 col.sort_order = Some(order - 1);
610 }
611 } else {
612 if order >= new_order {
614 col.sort_order = Some(order + 1);
615 }
616 }
617 }
618 }
619 self.columns[real_idx].sort_order = Some(new_order);
620
621 let mut current_sorted_cols: Vec<(&mut SortColumn, usize)> = self
623 .columns
624 .iter_mut()
625 .filter_map(|c| c.sort_order.map(|o| (c, o)))
626 .collect();
627 current_sorted_cols.sort_by_key(|(_, o)| *o);
628
629 for (i, (col, _)) in current_sorted_cols.into_iter().enumerate() {
630 col.sort_order = Some(i + 1);
631 }
632
633 if let Some(new_selected_idx) = self
635 .filtered_columns()
636 .iter()
637 .position(|&(r_idx, _)| r_idx == real_idx)
638 {
639 self.table_state.select(Some(new_selected_idx));
640 }
641 if old_order != Some(new_order) {
642 self.has_unapplied_changes = true;
643 }
644 } else if new_order == 0 {
645 if self.columns[real_idx].sort_order.take().is_some() {
647 self.has_unapplied_changes = true;
648 }
649 let mut current_sorted_cols: Vec<(&mut SortColumn, usize)> = self
651 .columns
652 .iter_mut()
653 .filter_map(|c| c.sort_order.map(|o| (c, o)))
654 .collect();
655 current_sorted_cols.sort_by_key(|(_, o)| *o);
656
657 for (i, (col, _)) in current_sorted_cols.into_iter().enumerate() {
658 col.sort_order = Some(i + 1);
659 }
660 if let Some(new_selected_idx) = self
662 .filtered_columns()
663 .iter()
664 .position(|&(r_idx, _)| r_idx == real_idx)
665 {
666 self.table_state.select(Some(new_selected_idx));
667 }
668 } else {
669 let range = if last == 1 {
671 "1".to_string()
672 } else {
673 format!("1-{last}")
674 };
675 self.status = Some(format!(
676 "Position {new_order} is past the end; use {range}."
677 ));
678 }
679 }
680 }
681 }
682}
683
684pub fn order_with_hidden(visible: &[String], all: &[String], reference: &[String]) -> Vec<String> {
689 use std::collections::HashSet;
690 let mut order = visible.to_vec();
691 let mut placed: HashSet<&str> = visible.iter().map(String::as_str).collect();
692 for name in all {
693 if placed.contains(name.as_str()) {
694 continue;
695 }
696 let earlier = match reference.iter().position(|r| r == name) {
697 Some(i) => &reference[..i],
698 None => &all[..all.iter().position(|a| a == name).unwrap_or(0)],
699 };
700 let at = earlier
701 .iter()
702 .rev()
703 .find(|e| placed.contains(e.as_str()))
704 .and_then(|e| order.iter().position(|o| o == e))
705 .map_or(0, |p| p + 1);
706 order.insert(at, name.clone());
707 placed.insert(name.as_str());
708 }
709 order
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715
716 fn columns(names: &[&str]) -> Vec<SortColumn> {
717 names
718 .iter()
719 .enumerate()
720 .map(|(i, name)| SortColumn {
721 name: name.to_string(),
722 sort_order: None,
723 sort_descending: false,
724 display_order: i,
725 is_locked: false,
726 is_to_be_locked: false,
727 is_visible: true,
728 width: WidthChoice::Auto,
729 shown_width: Some(10),
730 })
731 .collect()
732 }
733
734 #[test]
735 fn test_sort_modal_new() {
736 let modal = SortModal::new();
737 assert!(!modal.active);
738 assert_eq!(modal.filter_input.value(), "");
739 assert!(modal.columns.is_empty());
740 assert!(modal.table_state.selected().is_none());
741 }
742
743 #[test]
744 fn test_filtered_columns() {
745 let mut modal = SortModal::new();
746 modal.columns = columns(&["Apple", "Banana", "Orange"]);
747 modal.filter_input.set_value("an");
748 let filtered = modal.filtered_columns();
749 assert_eq!(filtered.len(), 2);
750 assert_eq!(filtered[0].1.name, "Banana");
751 assert_eq!(filtered[1].1.name, "Orange");
752 }
753
754 #[test]
757 fn space_cycles_a_column_through_the_three_states() {
758 let mut modal = SortModal::new();
759 modal.columns = columns(&["A", "B"]);
760 modal.table_state.select(Some(0));
761
762 modal.cycle_sort();
763 assert_eq!(modal.columns[0].sort_order, Some(1));
764 assert!(!modal.columns[0].sort_descending, "first press: ascending");
765
766 modal.cycle_sort();
767 assert_eq!(modal.columns[0].sort_order, Some(1));
768 assert!(modal.columns[0].sort_descending, "second press: descending");
769
770 modal.cycle_sort();
771 assert_eq!(modal.columns[0].sort_order, None, "third press: out");
772 assert!(!modal.columns[0].sort_descending);
773 }
774
775 #[test]
778 fn leaving_the_sort_renumbers_and_keeps_directions() {
779 let mut modal = SortModal::new();
780 modal.columns = columns(&["A", "B", "C"]);
781 modal.table_state.select(Some(1)); modal.cycle_sort();
783 modal.table_state.select(Some(0)); modal.cycle_sort();
785 modal.cycle_sort();
786 modal.table_state.select(Some(2)); modal.cycle_sort();
788
789 let (names, directions) = modal.sorted_columns_and_directions();
790 assert_eq!(names, ["B", "A", "C"]);
791 assert_eq!(directions, [false, true, false]);
792
793 modal.table_state.select(Some(1));
795 modal.cycle_sort();
796 modal.cycle_sort();
797 let (names, directions) = modal.sorted_columns_and_directions();
798 assert_eq!(names, ["A", "C"]);
799 assert_eq!(directions, [true, false]);
800 }
801
802 #[test]
805 fn zero_removes_a_column_and_stages_the_change() {
806 let mut modal = SortModal::new();
807 modal.columns = columns(&["A", "B"]);
808 modal.columns[0].sort_order = Some(1);
809 modal.table_state.select(Some(1));
810 modal.jump_selection_to_order(0);
811 assert!(!modal.has_unapplied_changes, "B was not sorted");
812
813 modal.table_state.select(Some(0));
814 modal.jump_selection_to_order(0);
815 assert_eq!(modal.columns[0].sort_order, None);
816 assert!(modal.has_unapplied_changes);
817 }
818
819 #[test]
822 fn a_digit_past_the_end_says_why() {
823 let mut modal = SortModal::new();
824 modal.columns = columns(&["A", "B", "C"]);
825 modal.columns[0].sort_order = Some(1);
826 modal.table_state.select(Some(1));
827 modal.jump_selection_to_order(5);
828 assert_eq!(modal.columns[1].sort_order, None);
829 assert!(!modal.has_unapplied_changes);
830 assert_eq!(
831 modal.status.as_deref(),
832 Some("Position 5 is past the end; use 1-2.")
833 );
834
835 modal.table_state.select(Some(0));
838 modal.jump_selection_to_order(2);
839 assert_eq!(
840 modal.status.as_deref(),
841 Some("Position 2 is past the end; use 1.")
842 );
843 modal.jump_selection_to_order(1);
844 assert_eq!(modal.columns[0].sort_order, Some(1));
845 assert!(!modal.has_unapplied_changes);
846 }
847
848 #[test]
849 fn test_move_selection_up() {
850 let mut modal = SortModal::new();
851 modal.columns = columns(&["A", "B"]);
852 modal.columns[0].sort_order = Some(2);
853 modal.columns[1].sort_order = Some(1);
854 modal.table_state.select(Some(0)); modal.move_selection_up();
856 assert_eq!(modal.columns[0].sort_order, Some(1));
857 assert_eq!(modal.columns[1].sort_order, Some(2));
858 }
859
860 #[test]
861 fn test_move_selection_down() {
862 let mut modal = SortModal::new();
863 modal.columns = columns(&["A", "B"]);
864 modal.columns[0].sort_order = Some(2);
865 modal.columns[1].sort_order = Some(1);
866 modal.table_state.select(Some(1)); modal.move_selection_down();
868 assert_eq!(modal.columns[0].sort_order, Some(1));
869 assert_eq!(modal.columns[1].sort_order, Some(2));
870 }
871
872 #[test]
873 fn the_sort_list_flips_moves_and_drops_entries() {
874 let mut modal = SortModal::new();
875 modal.columns = columns(&["A", "B", "C"]);
876 assert_eq!(modal.add_sort("C"), Some(0));
877 assert_eq!(modal.add_sort("A"), Some(1));
878 assert_eq!(
879 modal.add_sort("A"),
880 Some(1),
881 "a sorted column keeps its place"
882 );
883 assert_eq!(modal.get_sorted_columns(), ["C", "A"]);
884 modal.flip_sort(1);
885 assert_eq!(modal.sorted_columns_and_directions().1, [false, true]);
886 assert_eq!(modal.move_sort_entry(1, true), 0);
887 assert_eq!(modal.get_sorted_columns(), ["A", "C"]);
888 assert_eq!(modal.move_sort_entry(0, true), 0, "the first stays first");
889 modal.remove_sort_entry(0);
890 assert_eq!(modal.get_sorted_columns(), ["C"]);
891 assert_eq!(modal.columns[2].sort_order, Some(1), "renumbered");
892 }
893
894 #[test]
895 fn the_sort_cycles_both_ways() {
896 let mut modal = SortModal::new();
897 modal.columns = columns(&["A"]);
898 modal.table_state.select(Some(0));
899 modal.cycle_sort_back();
900 assert_eq!(modal.sorted_columns_and_directions().1, [true]);
901 modal.cycle_sort_back();
902 assert_eq!(modal.sorted_columns_and_directions().1, [false]);
903 modal.cycle_sort_back();
904 assert!(modal.get_sorted_columns().is_empty());
905 }
906
907 #[test]
908 fn test_clear_selection() {
909 let mut modal = SortModal::new();
910 modal.columns = columns(&["A", "B"]);
911 modal.columns[0].sort_order = Some(1);
912 modal.columns[0].sort_descending = true;
913 modal.columns[1].sort_order = Some(2);
914 modal.clear_selection();
915 assert!(modal.columns[0].sort_order.is_none());
916 assert!(!modal.columns[0].sort_descending);
917 assert!(modal.columns[1].sort_order.is_none());
918 }
919
920 #[test]
923 fn width_changes_are_staged_on_the_column_under_the_cursor() {
924 let mut modal = SortModal::new();
925 modal.columns = columns(&["a", "b"]);
926 modal.table_state.select(Some(1));
927 modal.change_width(WidthChoice::wider);
928 assert_eq!(modal.columns[1].width, WidthChoice::Manual(14));
929 assert!(modal.has_unapplied_changes);
930 modal.change_width(WidthChoice::narrower);
931 modal.change_width(WidthChoice::narrower);
932 assert_eq!(modal.columns[1].width, WidthChoice::Manual(6));
933 modal.change_width(|_, _| WidthChoice::Fit);
934 assert_eq!(modal.columns[1].width, WidthChoice::Fit);
935 modal.change_width(|_, _| WidthChoice::Auto);
936 assert_eq!(modal.columns[0].width, WidthChoice::Auto);
937 assert_eq!(
938 modal.width_choices(),
939 vec![
940 ("a".to_string(), WidthChoice::Auto),
941 ("b".to_string(), WidthChoice::Auto)
942 ]
943 );
944 modal.change_width(WidthChoice::wider);
945 modal.clear_selection();
946 assert_eq!(modal.columns[1].width, WidthChoice::Auto);
947 }
948
949 fn names(order: &[&str]) -> Vec<String> {
950 order.iter().map(|s| s.to_string()).collect()
951 }
952
953 #[test]
954 fn hiding_and_showing_keeps_the_column_in_place() {
955 let mut modal = SortModal::new();
956 modal.columns = columns(&["A", "B", "C", "D"]);
957 modal.table_state.select(Some(1));
958 modal.toggle_visibility();
959 assert_eq!(modal.get_column_order(), names(&["A", "C", "D"]));
960 let listed: Vec<&str> = modal
962 .filtered_columns()
963 .iter()
964 .map(|(_, c)| c.name.as_str())
965 .collect();
966 assert_eq!(listed, ["A", "B", "C", "D"]);
967 modal.toggle_visibility();
968 assert_eq!(modal.get_column_order(), names(&["A", "B", "C", "D"]));
969 }
970
971 #[test]
972 fn a_hidden_column_keeps_its_lock_but_is_not_counted() {
973 let mut modal = SortModal::new();
974 modal.columns = columns(&["A", "B", "C", "D"]);
975 modal.table_state.select(Some(2));
976 modal.toggle_lock_at_column();
977 assert_eq!(modal.get_locked_columns_count(), 3);
978 modal.table_state.select(Some(1));
979 modal.toggle_visibility();
980 assert_eq!(modal.get_locked_columns_count(), 2, "A and C stay frozen");
981 modal.toggle_visibility();
982 assert_eq!(modal.get_locked_columns_count(), 3, "B is frozen again");
983 }
984
985 #[test]
986 fn hidden_columns_go_back_after_the_column_they_followed() {
987 let all = names(&["a", "b", "c", "d", "e"]);
988 let reference = names(&["c", "a", "b", "d", "e"]);
990 assert_eq!(
991 order_with_hidden(&names(&["c", "a", "e"]), &all, &reference),
992 names(&["c", "a", "b", "d", "e"])
993 );
994 assert_eq!(
996 order_with_hidden(&names(&["c", "e"]), &all, &[]),
997 names(&["a", "b", "c", "d", "e"])
998 );
999 }
1000}