vkteams_bot/api/utils/
keyboard.rs

1//! Module with traits for [`Keyboard`], [`MessageTextParser`], etc.
2use crate::api::types::*;
3use std::convert::From;
4
5impl From<Keyboard> for String {
6    /// # Convert [`Keyboard`] to JSON string
7    fn from(val: Keyboard) -> Self {
8        val.get_keyboard()
9    }
10}
11impl Keyboard {
12    /// # Create new [`Keyboard`]
13    pub fn new() -> Self {
14        Self {
15            ..Default::default()
16        }
17    }
18    /// # Append row with buttons to [`Keyboard`]
19    pub fn add_row(&mut self) -> Self {
20        self.buttons.push(vec![]);
21        self.to_owned()
22    }
23    /// # Get index of last row of [`Keyboard`]
24    pub fn get_row_index(&self) -> usize {
25        self.buttons.len() - 1
26    }
27    /// # Append button to last row of [`Keyboard`]
28    /// Maximum buttons in row is 8. If row is full, add new row
29    pub fn add_button(&mut self, button: &ButtonKeyboard) -> Self {
30        // IF row is full, add new row
31        if self.buttons[self.get_row_index()].len() >= 8 {
32            self.add_row();
33        }
34        let row_index = self.get_row_index();
35        self.buttons[row_index].push(button.clone());
36        self.to_owned()
37    }
38    /// # Get keyboard as JSON string
39    fn get_keyboard(&self) -> String {
40        serde_json::to_string(&self.buttons).unwrap()
41    }
42}
43impl ButtonKeyboard {
44    /// Create new [`ButtonKeyboard`] with URL
45    /// ## Parameters
46    /// - `text`: [`String`] - Button text
47    /// - `url`: [`String`] - URL
48    /// - `style`: [`ButtonStyle`] - Button style
49    pub fn url(text: String, url: String, style: ButtonStyle) -> Self {
50        ButtonKeyboard {
51            text,
52            style: Some(style),
53            url: Some(url),
54            callback_data: None,
55        }
56    }
57    /// Create new [`ButtonKeyboard`] with callback data
58    /// ## Parameters
59    /// - `text`: [`String`] - Button text
60    /// - `cb`: [`String`] - Callback data
61    /// - `style`: [`ButtonStyle`] - Button style
62    pub fn cb(text: String, cb: String, style: ButtonStyle) -> Self {
63        ButtonKeyboard {
64            text,
65            style: Some(style),
66            url: None,
67            callback_data: Some(cb),
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::api::types::ButtonStyle;
76
77    #[test]
78    fn test_keyboard_new_and_add_row() {
79        let mut kb = Keyboard::new();
80        assert_eq!(kb.buttons.len(), 1);
81        kb = kb.add_row();
82        assert_eq!(kb.buttons.len(), 2);
83        kb = kb.add_row();
84        assert_eq!(kb.buttons.len(), 3);
85    }
86
87    #[test]
88    fn test_keyboard_add_button_and_row_limits() {
89        let mut kb = Keyboard::new();
90        let btn = ButtonKeyboard::url(
91            "A".to_string(),
92            "http://a".to_string(),
93            ButtonStyle::Primary,
94        );
95        // Добавляем 8 кнопок в одну строку
96        for _ in 0..8 {
97            kb = kb.add_button(&btn);
98        }
99        assert_eq!(kb.buttons.len(), 1);
100        assert_eq!(kb.buttons[0].len(), 8);
101        // 9-я кнопка должна создать новую строку
102        kb = kb.add_button(&btn);
103        assert_eq!(kb.buttons.len(), 2);
104        assert_eq!(kb.buttons[1].len(), 1);
105    }
106
107    #[test]
108    fn test_keyboard_get_row_index() {
109        let mut kb = Keyboard::new();
110        assert_eq!(kb.get_row_index(), 0);
111        kb = kb.add_row();
112        assert_eq!(kb.get_row_index(), 1);
113    }
114
115    #[test]
116    fn test_keyboard_to_string_json() {
117        let mut kb = Keyboard::new();
118        let btn = ButtonKeyboard::cb("B".to_string(), "cb".to_string(), ButtonStyle::Attention);
119        kb = kb.add_button(&btn);
120        let json: String = kb.clone().into();
121        // Должен быть валидный JSON массив массивов
122        let parsed: Vec<Vec<ButtonKeyboard>> = serde_json::from_str(&json).unwrap();
123        assert_eq!(parsed.len(), kb.buttons.len());
124        assert_eq!(parsed[0][0].text, "B");
125    }
126
127    #[test]
128    fn test_buttonkeyboard_url_and_cb() {
129        let btn_url = ButtonKeyboard::url(
130            "Link".to_string(),
131            "http://link".to_string(),
132            ButtonStyle::Base,
133        );
134        assert_eq!(btn_url.text, "Link");
135        assert_eq!(btn_url.url.as_deref(), Some("http://link"));
136        assert!(btn_url.callback_data.is_none());
137        let btn_cb =
138            ButtonKeyboard::cb("CB".to_string(), "data".to_string(), ButtonStyle::Attention);
139        assert_eq!(btn_cb.text, "CB");
140        assert_eq!(btn_cb.callback_data.as_deref(), Some("data"));
141        assert!(btn_cb.url.is_none());
142    }
143
144    #[test]
145    fn test_keyboard_add_many_buttons_multiple_rows() {
146        let mut kb = Keyboard::new();
147        let btn = ButtonKeyboard::url(
148            "A".to_string(),
149            "http://a".to_string(),
150            ButtonStyle::Primary,
151        );
152        for _ in 0..17 {
153            kb = kb.add_button(&btn);
154        }
155        // Должно быть 3 строки: 8 + 8 + 1 (с учётом начальной строки)
156        assert_eq!(kb.buttons.len(), 3);
157        assert_eq!(kb.buttons[0].len(), 8);
158        assert_eq!(kb.buttons[1].len(), 8);
159        assert_eq!(kb.buttons[2].len(), 1);
160    }
161}