Skip to content

Commit 917afaa

Browse files
ryanoneillclaude
andcommitted
Fix LoadingList::selected() to return item reference
Rename the old selected() -> Option<usize> to selected_index() and add a new selected() -> Option<&LoadingListItem<T>> for consistency with other Envision components (Tabs, RadioGroup) that return item references from their selected() method. - Rename selected() to selected_index() (returns Option<usize>) - Add selected() returning Option<&LoadingListItem<T>> - Make selected_item() an alias for selected() - Update all test call sites (~27) from selected() to selected_index() - Add test_selected_returns_item test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 087bee6 commit 917afaa

File tree

4 files changed

+77
-30
lines changed

4 files changed

+77
-30
lines changed

src/component/loading_list/mod.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,15 +353,43 @@ impl<T: Clone> LoadingListState<T> {
353353
}
354354

355355
/// Returns the selected index.
356-
pub fn selected(&self) -> Option<usize> {
356+
pub fn selected_index(&self) -> Option<usize> {
357357
self.selected
358358
}
359359

360360
/// Returns the selected item.
361-
pub fn selected_item(&self) -> Option<&LoadingListItem<T>> {
361+
///
362+
/// This is the canonical `selected()` method consistent with other Envision
363+
/// components (Tabs, RadioGroup, etc.) that return `Option<&T>`.
364+
///
365+
/// # Example
366+
///
367+
/// ```rust
368+
/// use envision::component::LoadingListState;
369+
///
370+
/// #[derive(Clone)]
371+
/// struct Task { name: String }
372+
///
373+
/// let mut state = LoadingListState::with_items(
374+
/// vec![Task { name: "Build".to_string() }],
375+
/// |t| t.name.clone(),
376+
/// );
377+
/// assert!(state.selected().is_none());
378+
///
379+
/// state.set_selected(Some(0));
380+
/// assert_eq!(state.selected().unwrap().label(), "Build");
381+
/// ```
382+
pub fn selected(&self) -> Option<&LoadingListItem<T>> {
362383
self.selected.and_then(|i| self.items.get(i))
363384
}
364385

386+
/// Returns the selected item.
387+
///
388+
/// Alias for [`selected()`](Self::selected).
389+
pub fn selected_item(&self) -> Option<&LoadingListItem<T>> {
390+
self.selected()
391+
}
392+
365393
/// Returns the selected item's data.
366394
pub fn selected_data(&self) -> Option<&T> {
367395
self.selected_item().map(|item| item.data())

src/component/loading_list/tests/component.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ fn test_update_down() {
103103
let mut state = LoadingListState::with_items(items, |i| i.name.clone());
104104

105105
LoadingList::update(&mut state, LoadingListMessage::Down);
106-
assert_eq!(state.selected(), Some(0));
106+
assert_eq!(state.selected_index(), Some(0));
107107

108108
LoadingList::update(&mut state, LoadingListMessage::Down);
109-
assert_eq!(state.selected(), Some(1));
109+
assert_eq!(state.selected_index(), Some(1));
110110
}
111111

112112
#[test]
@@ -116,7 +116,7 @@ fn test_update_down_wrap() {
116116
state.set_selected(Some(2)); // Last item
117117

118118
LoadingList::update(&mut state, LoadingListMessage::Down);
119-
assert_eq!(state.selected(), Some(0)); // Wraps
119+
assert_eq!(state.selected_index(), Some(0)); // Wraps
120120
}
121121

122122
#[test]
@@ -126,7 +126,7 @@ fn test_update_up() {
126126
state.set_selected(Some(2));
127127

128128
LoadingList::update(&mut state, LoadingListMessage::Up);
129-
assert_eq!(state.selected(), Some(1));
129+
assert_eq!(state.selected_index(), Some(1));
130130
}
131131

132132
#[test]
@@ -136,7 +136,7 @@ fn test_update_up_wrap() {
136136
state.set_selected(Some(0));
137137

138138
LoadingList::update(&mut state, LoadingListMessage::Up);
139-
assert_eq!(state.selected(), Some(2)); // Wraps
139+
assert_eq!(state.selected_index(), Some(2)); // Wraps
140140
}
141141

142142
#[test]
@@ -146,7 +146,7 @@ fn test_update_first() {
146146
state.set_selected(Some(2));
147147

148148
LoadingList::update(&mut state, LoadingListMessage::First);
149-
assert_eq!(state.selected(), Some(0));
149+
assert_eq!(state.selected_index(), Some(0));
150150
}
151151

152152
#[test]
@@ -155,7 +155,7 @@ fn test_update_last() {
155155
let mut state = LoadingListState::with_items(items, |i| i.name.clone());
156156

157157
LoadingList::update(&mut state, LoadingListMessage::Last);
158-
assert_eq!(state.selected(), Some(2));
158+
assert_eq!(state.selected_index(), Some(2));
159159
}
160160

161161
#[test]
@@ -323,7 +323,7 @@ fn test_update_set_items() {
323323
LoadingList::update(&mut state, LoadingListMessage::SetItems(items));
324324

325325
assert_eq!(state.len(), 3);
326-
assert!(state.selected().is_none()); // Selection cleared
326+
assert!(state.selected_index().is_none()); // Selection cleared
327327
assert_eq!(state.items()[0].label(), "Item 1"); // Uses default labeling
328328
}
329329

@@ -362,7 +362,7 @@ fn test_up_no_selection() {
362362
// No selection set
363363

364364
let output = LoadingList::update(&mut state, LoadingListMessage::Up);
365-
assert_eq!(state.selected(), Some(2)); // Goes to last item
365+
assert_eq!(state.selected_index(), Some(2)); // Goes to last item
366366
assert!(matches!(
367367
output,
368368
Some(LoadingListOutput::SelectionChanged(2))
@@ -376,7 +376,7 @@ fn test_down_no_selection() {
376376
// No selection set
377377

378378
let output = LoadingList::update(&mut state, LoadingListMessage::Down);
379-
assert_eq!(state.selected(), Some(0)); // Goes to first item
379+
assert_eq!(state.selected_index(), Some(0)); // Goes to first item
380380
assert!(matches!(
381381
output,
382382
Some(LoadingListOutput::SelectionChanged(0))
@@ -537,15 +537,15 @@ fn test_mixed_item_states_with_navigation() {
537537

538538
// Navigate through all items regardless of state
539539
LoadingList::update(&mut state, LoadingListMessage::Down);
540-
assert_eq!(state.selected(), Some(0));
540+
assert_eq!(state.selected_index(), Some(0));
541541
assert!(state.selected_item().unwrap().is_loading());
542542

543543
LoadingList::update(&mut state, LoadingListMessage::Down);
544-
assert_eq!(state.selected(), Some(1));
544+
assert_eq!(state.selected_index(), Some(1));
545545
assert!(state.selected_item().unwrap().is_error());
546546

547547
LoadingList::update(&mut state, LoadingListMessage::Down);
548-
assert_eq!(state.selected(), Some(2));
548+
assert_eq!(state.selected_index(), Some(2));
549549
assert!(state.selected_item().unwrap().is_ready());
550550

551551
// Select works on items in any state
@@ -568,24 +568,24 @@ fn test_large_loading_list_navigation() {
568568

569569
// Navigate through them (wrapping navigation, starts with no selection)
570570
LoadingList::update(&mut state, LoadingListMessage::Down);
571-
assert_eq!(state.selected(), Some(0));
571+
assert_eq!(state.selected_index(), Some(0));
572572

573573
for _ in 0..50 {
574574
LoadingList::update(&mut state, LoadingListMessage::Down);
575575
}
576-
assert_eq!(state.selected(), Some(50));
576+
assert_eq!(state.selected_index(), Some(50));
577577

578578
LoadingList::update(&mut state, LoadingListMessage::First);
579-
assert_eq!(state.selected(), Some(0));
579+
assert_eq!(state.selected_index(), Some(0));
580580

581581
LoadingList::update(&mut state, LoadingListMessage::Last);
582-
assert_eq!(state.selected(), Some(99));
582+
assert_eq!(state.selected_index(), Some(99));
583583

584584
// Down from last wraps to first
585585
LoadingList::update(&mut state, LoadingListMessage::Down);
586-
assert_eq!(state.selected(), Some(0));
586+
assert_eq!(state.selected_index(), Some(0));
587587

588588
// Up from first wraps to last
589589
LoadingList::update(&mut state, LoadingListMessage::Up);
590-
assert_eq!(state.selected(), Some(99));
590+
assert_eq!(state.selected_index(), Some(99));
591591
}

src/component/loading_list/tests/events.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn test_dispatch_event() {
7474
output,
7575
Some(LoadingListOutput::SelectionChanged(0))
7676
));
77-
assert_eq!(state.selected(), Some(0));
77+
assert_eq!(state.selected_index(), Some(0));
7878
}
7979

8080
// ========================================
@@ -97,15 +97,15 @@ fn test_instance_methods() {
9797
output,
9898
Some(LoadingListOutput::SelectionChanged(0))
9999
));
100-
assert_eq!(state.selected(), Some(0));
100+
assert_eq!(state.selected_index(), Some(0));
101101

102102
// instance dispatch_event
103103
let output = state.dispatch_event(&Event::key(KeyCode::Down));
104104
assert!(matches!(
105105
output,
106106
Some(LoadingListOutput::SelectionChanged(1))
107107
));
108-
assert_eq!(state.selected(), Some(1));
108+
assert_eq!(state.selected_index(), Some(1));
109109
}
110110

111111
// ========================================
@@ -152,7 +152,7 @@ fn test_disabled_prevents_navigation() {
152152

153153
let output = LoadingList::<TestItem>::update(&mut state, LoadingListMessage::Down);
154154
assert_eq!(output, None);
155-
assert_eq!(state.selected(), None);
155+
assert_eq!(state.selected_index(), None);
156156

157157
let output = LoadingList::<TestItem>::update(&mut state, LoadingListMessage::Up);
158158
assert_eq!(output, None);

src/component/loading_list/tests/state.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use super::*;
88
fn test_state_new() {
99
let state: LoadingListState<String> = LoadingListState::new();
1010
assert!(state.is_empty());
11-
assert!(state.selected().is_none());
11+
assert!(state.selected_index().is_none());
1212
assert!(state.show_indicators());
1313
}
1414

@@ -83,23 +83,42 @@ fn test_state_counts() {
8383
}
8484

8585
#[test]
86-
fn test_state_selected() {
86+
fn test_state_selected_index() {
8787
let items = make_items();
8888
let mut state = LoadingListState::with_items(items, |i| i.name.clone());
8989

9090
state.set_selected(Some(1));
91-
assert_eq!(state.selected(), Some(1));
91+
assert_eq!(state.selected_index(), Some(1));
9292
assert_eq!(state.selected_item().unwrap().label(), "Item Two");
9393
assert_eq!(state.selected_data().unwrap().id, 2);
9494
}
9595

96+
#[test]
97+
fn test_selected_returns_item() {
98+
let items = make_items();
99+
let mut state = LoadingListState::with_items(items, |i| i.name.clone());
100+
101+
// No selection returns None
102+
assert!(state.selected().is_none());
103+
104+
// With selection returns the item
105+
state.set_selected(Some(0));
106+
let item = state.selected().unwrap();
107+
assert_eq!(item.label(), "Item One");
108+
assert_eq!(item.data().id, 1);
109+
110+
// selected() and selected_item() return the same thing
111+
state.set_selected(Some(2));
112+
assert_eq!(state.selected().unwrap().label(), state.selected_item().unwrap().label());
113+
}
114+
96115
#[test]
97116
fn test_state_selected_clamped() {
98117
let items = make_items();
99118
let mut state = LoadingListState::with_items(items, |i| i.name.clone());
100119

101120
state.set_selected(Some(100)); // Too high
102-
assert_eq!(state.selected(), Some(2)); // Clamped to last
121+
assert_eq!(state.selected_index(), Some(2)); // Clamped to last
103122
}
104123

105124
#[test]
@@ -119,7 +138,7 @@ fn test_state_clear() {
119138

120139
state.clear();
121140
assert!(state.is_empty());
122-
assert!(state.selected().is_none());
141+
assert!(state.selected_index().is_none());
123142
}
124143

125144
#[test]

0 commit comments

Comments
 (0)