vantage_table/pagination.rs
1use vantage_expressions::traits::selectable::Selectable;
2
3/// Pagination configuration for tables
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Pagination {
6 page: i64,
7 items_per_page: i64,
8}
9
10impl Pagination {
11 /// Create new pagination with page number and items per page
12 pub fn new(page: i64, items_per_page: i64) -> Self {
13 Self {
14 page: page.max(1),
15 items_per_page: items_per_page.max(1),
16 }
17 }
18
19 /// Set the current page number (1-based)
20 pub fn set_page(&mut self, page: i64) {
21 self.page = page.max(1);
22 }
23
24 /// Set items per page
25 /// When changing page size, adjusts current page to keep focused item visible
26 pub fn set_ipp(&mut self, items_per_page: i64) {
27 let items_per_page = items_per_page.max(1);
28
29 // Calculate which item is currently at the top of the page
30 let first_item_index = (self.page - 1) * self.items_per_page;
31
32 // Calculate which page that item would be on with the new page size
33 self.page = (first_item_index / items_per_page) + 1;
34 self.items_per_page = items_per_page;
35 }
36
37 /// Get the current page number (1-based)
38 pub fn get_page(&self) -> i64 {
39 self.page
40 }
41
42 /// Get items per page
43 pub fn get_ipp(&self) -> i64 {
44 self.items_per_page
45 }
46
47 /// Calculate limit value for queries
48 pub fn limit(&self) -> i64 {
49 self.items_per_page
50 }
51
52 /// Calculate skip/offset value for queries
53 pub fn skip(&self) -> i64 {
54 (self.page - 1) * self.items_per_page
55 }
56
57 /// Apply pagination to a select query
58 pub fn apply_on_select<S, E>(&self, select: &mut S)
59 where
60 S: Selectable<E>,
61 {
62 select.set_limit(Some(self.limit()), Some(self.skip()));
63 }
64}
65
66impl Default for Pagination {
67 fn default() -> Self {
68 Self {
69 page: 1,
70 items_per_page: 50,
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_new_pagination() {
81 let p = Pagination::new(2, 25);
82 assert_eq!(p.get_page(), 2);
83 assert_eq!(p.get_ipp(), 25);
84 assert_eq!(p.limit(), 25);
85 assert_eq!(p.skip(), 25);
86 }
87
88 #[test]
89 fn test_pagination_bounds() {
90 let p = Pagination::new(0, 0);
91 assert_eq!(p.get_page(), 1);
92 assert_eq!(p.get_ipp(), 1);
93 }
94
95 #[test]
96 fn test_set_page() {
97 let mut p = Pagination::new(1, 10);
98 p.set_page(3);
99 assert_eq!(p.get_page(), 3);
100 assert_eq!(p.skip(), 20);
101 }
102
103 #[test]
104 fn test_set_ipp_keeps_focus() {
105 let mut p = Pagination::new(3, 10);
106 // Page 3 with 10 items means items 20-29 are visible
107 // First item is at index 20
108
109 p.set_ipp(5);
110 // With 5 items per page, item 20 should be on page 5 (items 20-24)
111 assert_eq!(p.get_page(), 5);
112 assert_eq!(p.get_ipp(), 5);
113 assert_eq!(p.skip(), 20);
114 }
115
116 #[test]
117 fn test_set_ipp_larger_page_size() {
118 let mut p = Pagination::new(5, 5);
119 // Page 5 with 5 items means items 20-24 are visible
120
121 p.set_ipp(25);
122 // With 25 items per page, item 20 should be on page 1 (items 0-24)
123 assert_eq!(p.get_page(), 1);
124 assert_eq!(p.get_ipp(), 25);
125 assert_eq!(p.skip(), 0);
126 }
127
128 #[test]
129 fn test_default_pagination() {
130 let p = Pagination::default();
131 assert_eq!(p.get_page(), 1);
132 assert_eq!(p.get_ipp(), 50);
133 assert_eq!(p.skip(), 0);
134 }
135
136 // #[test]
137 // fn test_with_table() {
138 // use crate::{Table, TableLike, any::AnyTable, mocks::MockTableSource};
139
140 // let ds = MockTableSource::new();
141 // let table = Table::new("users", ds);
142 // let mut any_table = AnyTable::new(table);
143
144 // // Set pagination using callback
145 // // Note: set_ipp before set_page to avoid page recalculation
146 // any_table.with_pagination(|p| {
147 // p.set_ipp(25);
148 // p.set_page(3);
149 // });
150
151 // let pagination = any_table.get_pagination().unwrap();
152 // assert_eq!(pagination.get_page(), 3);
153 // assert_eq!(pagination.get_ipp(), 25);
154 // assert_eq!(pagination.skip(), 50);
155 // assert_eq!(pagination.limit(), 25);
156 // }
157
158 // #[test]
159 // fn test_table_pagination_direct_set() {
160 // use crate::{Table, TableLike, mocks::MockTableSource};
161
162 // let ds = MockTableSource::new();
163 // let mut table = Table::new("products", ds);
164
165 // // Set pagination directly via TableLike trait
166 // table.set_pagination(Some(Pagination::new(2, 10)));
167
168 // let pagination = table.get_pagination().unwrap();
169 // assert_eq!(pagination.get_page(), 2);
170 // assert_eq!(pagination.get_ipp(), 10);
171
172 // // Remove pagination
173 // table.set_pagination(None);
174 // assert!(table.get_pagination().is_none());
175 // }
176}