Skip to main content

tui/widgets/
amount.rs

1//! Numeric amount entry widget.
2//!
3//! Wraps an [`Editor`] and restricts inserted characters to the subset
4//! valid for rational amounts: digits, `-`, `.`, `/`.
5
6use super::editor::{EditMode, Editor};
7
8/// A text field accepting only amount characters (digits, `-`, `.`, `/`).
9#[derive(Debug, Clone)]
10pub struct AmountWidget {
11    editor: Editor,
12}
13
14impl AmountWidget {
15    #[must_use]
16    pub fn new(mode: EditMode) -> Self {
17        Self {
18            editor: Editor::new(mode),
19        }
20    }
21
22    #[must_use]
23    pub fn with_value(mode: EditMode, value: impl Into<String>) -> Self {
24        Self {
25            editor: Editor::with_buffer(mode, value),
26        }
27    }
28
29    /// Buffer text (the current amount string).
30    #[must_use]
31    pub fn value(&self) -> &str {
32        self.editor.buffer()
33    }
34
35    /// Insert `c` only if it is a valid amount character.
36    pub fn insert_char(&mut self, c: char) {
37        if is_amount_char(c) {
38            self.editor.insert_char(c);
39        }
40    }
41
42    /// Direct access to the inner editor for motion and delete operations.
43    #[must_use]
44    pub fn as_editor_mut(&mut self) -> &mut Editor {
45        &mut self.editor
46    }
47}
48
49fn is_amount_char(c: char) -> bool {
50    c.is_ascii_digit() || matches!(c, '-' | '.' | '/')
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn accepts_digits_and_separators() {
59        let mut w = AmountWidget::new(EditMode::Emacs);
60        for c in "100.5/3".chars() {
61            w.insert_char(c);
62        }
63        assert_eq!(w.value(), "100.5/3");
64    }
65
66    #[test]
67    fn accepts_negative_sign() {
68        let mut w = AmountWidget::new(EditMode::Emacs);
69        w.insert_char('-');
70        w.insert_char('5');
71        assert_eq!(w.value(), "-5");
72    }
73
74    #[test]
75    fn rejects_letters() {
76        let mut w = AmountWidget::new(EditMode::Emacs);
77        w.insert_char('a');
78        w.insert_char('b');
79        assert_eq!(w.value(), "");
80    }
81
82    #[test]
83    fn rejects_space_and_punctuation() {
84        let mut w = AmountWidget::new(EditMode::Emacs);
85        w.insert_char(' ');
86        w.insert_char('@');
87        w.insert_char('#');
88        assert_eq!(w.value(), "");
89    }
90
91    #[test]
92    fn delete_backward_via_editor_mut() {
93        let mut w = AmountWidget::new(EditMode::Emacs);
94        w.insert_char('5');
95        w.insert_char('0');
96        w.as_editor_mut().delete_backward();
97        assert_eq!(w.value(), "5");
98    }
99
100    #[test]
101    fn with_value_sets_initial_buffer() {
102        let w = AmountWidget::with_value(EditMode::Emacs, "42/7");
103        assert_eq!(w.value(), "42/7");
104    }
105}