Skip to main content

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}