Lines
100 %
Functions
66.67 %
Branches
//! Numeric amount entry widget.
//!
//! Wraps an [`Editor`] and restricts inserted characters to the subset
//! valid for rational amounts: digits, `-`, `.`, `/`.
use super::editor::{EditMode, Editor};
/// A text field accepting only amount characters (digits, `-`, `.`, `/`).
#[derive(Debug, Clone)]
pub struct AmountWidget {
editor: Editor,
}
impl AmountWidget {
#[must_use]
pub fn new(mode: EditMode) -> Self {
Self {
editor: Editor::new(mode),
pub fn with_value(mode: EditMode, value: impl Into<String>) -> Self {
editor: Editor::with_buffer(mode, value),
/// Buffer text (the current amount string).
pub fn value(&self) -> &str {
self.editor.buffer()
/// Insert `c` only if it is a valid amount character.
pub fn insert_char(&mut self, c: char) {
if is_amount_char(c) {
self.editor.insert_char(c);
/// Direct access to the inner editor for motion and delete operations.
pub fn as_editor_mut(&mut self) -> &mut Editor {
&mut self.editor
fn is_amount_char(c: char) -> bool {
c.is_ascii_digit() || matches!(c, '-' | '.' | '/')
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_digits_and_separators() {
let mut w = AmountWidget::new(EditMode::Emacs);
for c in "100.5/3".chars() {
w.insert_char(c);
assert_eq!(w.value(), "100.5/3");
fn accepts_negative_sign() {
w.insert_char('-');
w.insert_char('5');
assert_eq!(w.value(), "-5");
fn rejects_letters() {
w.insert_char('a');
w.insert_char('b');
assert_eq!(w.value(), "");
fn rejects_space_and_punctuation() {
w.insert_char(' ');
w.insert_char('@');
w.insert_char('#');
fn delete_backward_via_editor_mut() {
w.insert_char('0');
w.as_editor_mut().delete_backward();
assert_eq!(w.value(), "5");
fn with_value_sets_initial_buffer() {
let w = AmountWidget::with_value(EditMode::Emacs, "42/7");
assert_eq!(w.value(), "42/7");