fix: page up and down stop at the table edges instead of wrapping

A page is 20 rows and the index wrapped modulo one less than the row
count, so the last row was unreachable by paging, 21-row and 5-row tables
did nothing on page down from the first row, and 4-row tables cycled
0, 2, 1 and never reached row 3. Page down now clamps to the last row and
page up clamps to the first.

With no division left, a single-row table is safe, so the early return
only guards the empty table again, matching scroll_to_top and
scroll_to_bottom.
This commit is contained in:
Dark-Alex-17 committed 2026-09-17 17:38:37 -06:00
1 parent 1d936c5cb5
commit c42a46c7ca
4 files changed
+166 -47

No files matched your search

+9
View File
@@ -171,10 +171,19 @@ mod property_tests {
table.set_items(movies); table.set_items(movies);
for i in 0..page_ops { for i in 0..page_ops {
let previous_index = table.state.selected().unwrap();
if i % 2 == 0 { if i % 2 == 0 {
table.page_down(); table.page_down();
prop_assert_eq!(
table.state.selected(),
Some((previous_index + 20).min(list_size - 1))
);
} else { } else {
table.page_up(); table.page_up();
prop_assert_eq!(table.state.selected(), Some(previous_index.saturating_sub(20)));
} }
let current = table.current_selection(); let current = table.current_selection();
+3 -2
View File
@@ -606,17 +606,18 @@ mod tests {
.take(100) .take(100)
.collect(); .collect();
app.data.radarr_data.movies.set_items(movies_vec); app.data.radarr_data.movies.set_items(movies_vec);
app.data.radarr_data.movies.state.select(Some(50));
TableHandlerUnit::new(key, &mut app, ActiveRadarrBlock::Movies, None).handle(); TableHandlerUnit::new(key, &mut app, ActiveRadarrBlock::Movies, None).handle();
if key == Key::PgUp { if key == Key::PgUp {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.current_selection().title.text, app.data.radarr_data.movies.current_selection().title.text,
"Test 79" "Test 30"
); );
} else { } else {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.current_selection().title.text, app.data.radarr_data.movies.current_selection().title.text,
"Test 20" "Test 70"
); );
} }
} }
+4 -7
View File
@@ -100,29 +100,26 @@ where
{ {
fn page_down(&mut self) { fn page_down(&mut self) {
let items_len = self.active_items().len(); let items_len = self.active_items().len();
if items_len <= 1 { if items_len == 0 {
return; return;
} }
let state = self.active_state_mut(); let state = self.active_state_mut();
match state.selected() { match state.selected() {
Some(i) => state.select(Some(i.saturating_add(20) % (items_len - 1))), Some(i) => state.select(Some(i.saturating_add(20).min(items_len - 1))),
None => state.select_first(), None => state.select_first(),
} }
} }
fn page_up(&mut self) { fn page_up(&mut self) {
let items_len = self.active_items().len(); let items_len = self.active_items().len();
if items_len <= 1 { if items_len == 0 {
return; return;
} }
let state = self.active_state_mut(); let state = self.active_state_mut();
match state.selected() { match state.selected() {
Some(i) => { Some(i) => state.select(Some(i.saturating_sub(20))),
let len = items_len - 1;
state.select(Some((i + len - (20 % len)) % len));
}
None => state.select_last(), None => state.select_last(),
} }
} }
+150 -38
View File
@@ -318,6 +318,69 @@ mod tests {
20 20
); );
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
40
);
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
60
);
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
80
);
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
99
);
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
99
);
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
79
);
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
59
);
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
39
);
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
19
);
stateful_table.page_up(); stateful_table.page_up();
assert_some_eq_x!( assert_some_eq_x!(
@@ -327,33 +390,10 @@ mod tests {
stateful_table.page_up(); stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
stateful_table.filtered_items.as_ref().unwrap().len() - 21
);
stateful_table.page_down();
assert_some_eq_x!( assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(), stateful_table.filtered_state.as_ref().unwrap().selected(),
0 0
); );
stateful_table.scroll_down();
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
stateful_table.filtered_items.as_ref().unwrap().len() - 20
);
stateful_table.scroll_down();
stateful_table.page_down();
assert_some_eq_x!(
stateful_table.filtered_state.as_ref().unwrap().selected(),
2
);
} }
#[test] #[test]
@@ -376,33 +416,105 @@ mod tests {
assert_some_eq_x!(stateful_table.state.selected(), 20); assert_some_eq_x!(stateful_table.state.selected(), 20);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 40);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 60);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 80);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 99);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 99);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 79);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 59);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 39);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 19);
stateful_table.page_up(); stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 0); assert_some_eq_x!(stateful_table.state.selected(), 0);
stateful_table.page_up(); stateful_table.page_up();
assert_some_eq_x!( assert_some_eq_x!(stateful_table.state.selected(), 0);
stateful_table.state.selected(), }
stateful_table.items.len() - 21
);
stateful_table.page_down(); #[test]
fn test_stateful_table_pagination_on_twenty_one_item_table_stops_at_last_item() {
let mut stateful_table = StatefulTable::default();
stateful_table.set_items((0..21).collect());
assert_some_eq_x!(stateful_table.state.selected(), 0); assert_some_eq_x!(stateful_table.state.selected(), 0);
stateful_table.scroll_down();
stateful_table.page_up();
assert_some_eq_x!(
stateful_table.state.selected(),
stateful_table.items.len() - 20
);
stateful_table.scroll_down();
stateful_table.page_down(); stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 2); assert_some_eq_x!(stateful_table.state.selected(), 20);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 20);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 0);
}
#[test]
fn test_stateful_table_pagination_on_five_item_table_stops_at_last_item() {
let mut stateful_table = StatefulTable::default();
stateful_table.set_items((0..5).collect());
assert_some_eq_x!(stateful_table.state.selected(), 0);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 4);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 4);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 0);
}
#[test]
fn test_stateful_table_pagination_on_four_item_table_reaches_last_item() {
let mut stateful_table = StatefulTable::default();
stateful_table.set_items((0..4).collect());
assert_some_eq_x!(stateful_table.state.selected(), 0);
stateful_table.page_down();
assert_some_eq_x!(stateful_table.state.selected(), 3);
stateful_table.page_up();
assert_some_eq_x!(stateful_table.state.selected(), 0);
} }
#[test] #[test]