#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::any::Any;
use core::ffi::c_int;
use crate::ported::crt::{ColorElements, ColorScheme};
use crate::ported::object::{Object, ObjectClass, Object_class};
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendWide, RichString_appendnAscii,
RichString_writeAscii,
};
pub const NUMBERITEM_EDIT_MAX: usize = 10;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum OptionItemType {
OPTION_ITEM_TEXT = 0,
OPTION_ITEM_CHECK = 1,
OPTION_ITEM_NUMBER = 2,
}
pub fn OptionItem_kind(this: &dyn Object) -> OptionItemType {
let any: &dyn Any = this;
if any.is::<TextItem>() {
OptionItemType::OPTION_ITEM_TEXT
} else if any.is::<CheckItem>() {
OptionItemType::OPTION_ITEM_CHECK
} else if any.is::<NumberItem>() {
OptionItemType::OPTION_ITEM_NUMBER
} else {
panic!("OptionItem_kind: object is not a TextItem/CheckItem/NumberItem");
}
}
pub struct TextItem {
pub text: String,
}
pub struct CheckItem {
pub value: bool,
pub ref_: *mut bool,
pub text: String,
}
pub struct NumberItem {
pub value: i32,
pub ref_: *mut c_int,
pub scale: i32,
pub min: i32,
pub max: i32,
pub editing: bool,
pub editBuffer: String,
pub savedValue: i32,
pub text: String,
}
pub fn OptionItem_delete(this: Box<dyn Object>) {
let _ = this;
}
pub fn TextItem_display(this: &TextItem, out: &mut RichString) {
let help_bold = ColorElements::HELP_BOLD.packed(ColorScheme::active());
RichString_appendWide(out, help_bold, this.text.as_bytes());
}
pub fn CheckItem_display(this: &CheckItem, out: &mut RichString) {
let scheme = ColorScheme::active();
let check_box = ColorElements::CHECK_BOX.packed(scheme);
let check_mark = ColorElements::CHECK_MARK.packed(scheme);
let check_text = ColorElements::CHECK_TEXT.packed(scheme);
RichString_writeAscii(out, check_box, b"[");
if CheckItem_get(this) {
RichString_appendAscii(out, check_mark, b"x");
} else {
RichString_appendAscii(out, check_mark, b" ");
}
RichString_appendAscii(out, check_box, b"] ");
RichString_appendWide(out, check_text, this.text.as_bytes());
}
pub fn NumberItem_display(this: &NumberItem, out: &mut RichString) {
let scheme = ColorScheme::active();
let check_box = ColorElements::CHECK_BOX.packed(scheme);
let check_mark = ColorElements::CHECK_MARK.packed(scheme);
let check_text = ColorElements::CHECK_TEXT.packed(scheme);
RichString_writeAscii(out, check_box, b"[");
let written: usize;
if this.editing {
written = this.editBuffer.len();
RichString_appendnAscii(out, check_mark, this.editBuffer.as_bytes(), written);
} else if this.scale < 0 {
let buffer = format!(
"{:.*}",
(-this.scale) as usize,
10f64.powi(this.scale) * NumberItem_get(this) as f64
);
written = buffer.len();
RichString_appendnAscii(out, check_mark, buffer.as_bytes(), written);
} else if this.scale > 0 {
let buffer = format!(
"{}",
(10f64.powi(this.scale) * NumberItem_get(this) as f64) as i32
);
written = buffer.len();
RichString_appendnAscii(out, check_mark, buffer.as_bytes(), written);
} else {
let buffer = format!("{}", NumberItem_get(this));
written = buffer.len();
RichString_appendnAscii(out, check_mark, buffer.as_bytes(), written);
}
RichString_appendAscii(out, check_box, b"]");
for _ in written..5 {
RichString_appendAscii(out, check_box, b" ");
}
RichString_appendWide(out, check_text, this.text.as_bytes());
}
static OptionItem_class: ObjectClass = ObjectClass {
extends: Some(&Object_class),
};
static TextItem_class: ObjectClass = ObjectClass {
extends: Some(&OptionItem_class),
};
static CheckItem_class: ObjectClass = ObjectClass {
extends: Some(&OptionItem_class),
};
static NumberItem_class: ObjectClass = ObjectClass {
extends: Some(&OptionItem_class),
};
impl Object for TextItem {
fn klass(&self) -> &'static ObjectClass {
&TextItem_class
}
fn display(&self, out: &mut RichString) {
TextItem_display(self, out);
}
}
impl Object for CheckItem {
fn klass(&self) -> &'static ObjectClass {
&CheckItem_class
}
fn display(&self, out: &mut RichString) {
CheckItem_display(self, out);
}
}
impl Object for NumberItem {
fn klass(&self) -> &'static ObjectClass {
&NumberItem_class
}
fn display(&self, out: &mut RichString) {
NumberItem_display(self, out);
}
}
pub fn TextItem_new(text: &str) -> TextItem {
TextItem {
text: text.to_string(),
}
}
pub fn CheckItem_newByRef(text: &str, ref_: *mut bool) -> CheckItem {
CheckItem {
value: false,
ref_,
text: text.to_string(),
}
}
pub fn CheckItem_newByVal(text: &str, value: bool) -> CheckItem {
CheckItem {
value,
ref_: core::ptr::null_mut(),
text: text.to_string(),
}
}
pub fn CheckItem_get(this: &CheckItem) -> bool {
if !this.ref_.is_null() {
unsafe { *this.ref_ }
} else {
this.value
}
}
pub fn CheckItem_set(this: &mut CheckItem, value: bool) {
if !this.ref_.is_null() {
unsafe { *this.ref_ = value };
} else {
this.value = value;
}
}
pub fn CheckItem_toggle(this: &mut CheckItem) {
if !this.ref_.is_null() {
unsafe { *this.ref_ = !*this.ref_ };
} else {
this.value = !this.value;
}
}
pub fn NumberItem_newByRef(
text: &str,
ref_: *mut c_int,
scale: i32,
min: i32,
max: i32,
) -> NumberItem {
assert!(min <= max);
NumberItem {
value: 0,
ref_,
scale,
min,
max,
editing: false,
editBuffer: String::new(),
savedValue: 0,
text: text.to_string(),
}
}
pub fn NumberItem_newByVal(text: &str, value: i32, scale: i32, min: i32, max: i32) -> NumberItem {
assert!(min <= max);
let value = if value > max {
max
} else if value < min {
min
} else {
value
};
NumberItem {
value,
ref_: core::ptr::null_mut(),
scale,
min,
max,
editing: false,
editBuffer: String::new(),
savedValue: 0,
text: text.to_string(),
}
}
pub fn NumberItem_get(this: &NumberItem) -> i32 {
if !this.ref_.is_null() {
unsafe { *this.ref_ }
} else {
this.value
}
}
pub fn NumberItem_decrease(this: &mut NumberItem) {
if !this.ref_.is_null() {
unsafe {
let v = *this.ref_ - 1;
*this.ref_ = if v > this.max {
this.max
} else if v < this.min {
this.min
} else {
v
};
}
} else {
let v = this.value - 1;
this.value = if v > this.max {
this.max
} else if v < this.min {
this.min
} else {
v
};
}
}
pub fn NumberItem_increase(this: &mut NumberItem) {
if !this.ref_.is_null() {
unsafe {
let v = *this.ref_ + 1;
*this.ref_ = if v > this.max {
this.max
} else if v < this.min {
this.min
} else {
v
};
}
} else {
let v = this.value + 1;
this.value = if v > this.max {
this.max
} else if v < this.min {
this.min
} else {
v
};
}
}
pub fn NumberItem_toggle(this: &mut NumberItem) {
if !this.ref_.is_null() {
unsafe {
if *this.ref_ >= this.max {
*this.ref_ = this.min;
} else {
*this.ref_ += 1;
}
}
} else if this.value >= this.max {
this.value = this.min;
} else {
this.value += 1;
}
}
pub fn NumberItem_startEditing(this: &mut NumberItem) {
this.savedValue = NumberItem_get(this);
this.editing = true;
this.editBuffer.clear();
}
pub fn NumberItem_startEditingFromValue(this: &mut NumberItem) {
this.savedValue = NumberItem_get(this);
this.editing = true;
let tmp = if this.scale < 0 {
format!(
"{:.*}",
(-this.scale) as usize,
10f64.powi(this.scale) * this.savedValue as f64
)
} else {
format!("{}", this.savedValue)
};
let edit_len = tmp.len().min(NUMBERITEM_EDIT_MAX);
this.editBuffer = tmp[..edit_len].to_string();
}
pub fn NumberItem_cancelEditing(this: &mut NumberItem) {
this.editing = false;
this.editBuffer.clear();
}
pub fn NumberItem_applyEditing(this: &mut NumberItem) -> bool {
this.editing = false;
if this.editBuffer.is_empty() {
return false;
}
let bytes = this.editBuffer.as_bytes();
let mut i = 0;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
let conv_start = i;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let new_value: i32;
if this.scale < 0 {
let mut has_digit = false;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
has_digit = true;
}
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
has_digit = true;
}
}
if !has_digit {
this.editBuffer.clear();
return false;
}
let display_value: f64 = this.editBuffer[conv_start..i].parse().unwrap_or(0.0);
new_value = (display_value / 10f64.powi(this.scale)).round() as i32;
} else {
let digit_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == digit_start {
this.editBuffer.clear();
return false;
}
let parsed: i64 = this.editBuffer[conv_start..i].parse().unwrap_or(0);
new_value = parsed as i32;
}
let clamped = if new_value > this.max {
this.max
} else if new_value < this.min {
this.min
} else {
new_value
};
if !this.ref_.is_null() {
unsafe { *this.ref_ = clamped };
} else {
this.value = clamped;
}
this.editBuffer.clear();
true
}
pub fn NumberItem_addChar(this: &mut NumberItem, c: char) -> bool {
let mut c = c;
if c == ',' {
c = '.';
}
if c == '.' {
if this.scale >= 0 {
return false;
}
if this.editBuffer.contains('.') {
return false;
}
} else if !c.is_ascii_digit() {
return false;
}
if this.editBuffer.len() >= NUMBERITEM_EDIT_MAX {
return false;
}
this.editBuffer.push(c);
true
}
pub fn NumberItem_deleteChar(this: &mut NumberItem) {
this.editBuffer.pop();
}
#[cfg(test)]
mod tests {
use super::*;
fn number_item(value: i32, scale: i32, min: i32, max: i32) -> NumberItem {
NumberItem {
value,
ref_: core::ptr::null_mut(),
scale,
min,
max,
editing: false,
editBuffer: String::new(),
savedValue: 0,
text: String::new(),
}
}
fn rendered(rs: &RichString) -> String {
rs.chptr
.iter()
.take(rs.chlen as usize)
.map(|c| c.chars)
.collect()
}
#[test]
fn check_item_by_ref_reads_and_writes_external_cell() {
let mut cell: bool = false;
let mut it = CheckItem_newByRef("Follow", &mut cell as *mut bool);
assert!(!it.value);
assert!(!CheckItem_get(&it));
CheckItem_set(&mut it, true);
assert!(cell, "set writes the external cell");
assert!(CheckItem_get(&it));
assert!(!it.value, "the item's own value is never touched");
CheckItem_toggle(&mut it);
assert!(!cell, "toggle flips the external cell");
assert!(!CheckItem_get(&it));
cell = true;
assert!(CheckItem_get(&it) && cell);
}
#[test]
fn number_item_by_ref_reads_and_writes_external_cell() {
let mut cell: c_int = 5;
let mut it = NumberItem_newByRef("Delay", &mut cell as *mut c_int, 0, 0, 10);
assert_eq!(it.value, 0);
assert_eq!(NumberItem_get(&it), 5);
NumberItem_increase(&mut it);
assert_eq!(cell, 6);
NumberItem_decrease(&mut it);
assert_eq!(cell, 5);
assert_eq!(it.value, 0, "the item's own value is never touched");
cell = 10;
NumberItem_increase(&mut it);
assert_eq!(cell, 10);
cell = 0;
NumberItem_decrease(&mut it);
assert_eq!(cell, 0);
cell = 10;
NumberItem_toggle(&mut it);
assert_eq!(cell, 0);
NumberItem_toggle(&mut it);
assert_eq!(cell, 1);
}
#[test]
fn number_item_by_ref_apply_editing_commits_to_external_cell() {
let mut cell: c_int = 0;
let mut it = NumberItem_newByRef("N", &mut cell as *mut c_int, 0, 0, 50);
it.editBuffer = "27".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(cell, 27, "committed to the external cell");
assert_eq!(it.value, 0, "not the item's own value");
it.editBuffer = "999".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(cell, 50);
}
#[test]
fn check_item_get_set_toggle() {
let mut it = CheckItem {
value: false,
ref_: core::ptr::null_mut(),
text: String::new(),
};
assert!(!CheckItem_get(&it));
CheckItem_set(&mut it, true);
assert!(CheckItem_get(&it));
CheckItem_toggle(&mut it);
assert!(!CheckItem_get(&it));
CheckItem_toggle(&mut it);
assert!(CheckItem_get(&it));
}
fn attr_of(el: ColorElements) -> i32 {
el.packed(ColorScheme::active()) & 0xffffff
}
#[test]
fn text_item_display_renders_label_in_help_bold() {
let it = TextItem {
text: "General".to_string(),
};
let mut rs = RichString::new();
TextItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "General");
for i in 0..rs.chlen as usize {
assert_eq!(
rs.chptr[i].attr,
attr_of(ColorElements::HELP_BOLD),
"attr at {i}"
);
}
}
#[test]
fn check_item_display_checked_and_unchecked_glyphs() {
let checked = CheckItem {
value: true,
ref_: core::ptr::null_mut(),
text: "Tree view".to_string(),
};
let mut rs = RichString::new();
CheckItem_display(&checked, &mut rs);
assert_eq!(rendered(&rs), "[x] Tree view");
let unchecked = CheckItem {
value: false,
ref_: core::ptr::null_mut(),
text: "Tree view".to_string(),
};
let mut rs2 = RichString::new();
CheckItem_display(&unchecked, &mut rs2);
assert_eq!(rendered(&rs2), "[ ] Tree view");
}
#[test]
fn check_item_display_attrs_per_cell() {
let it = CheckItem {
value: true,
ref_: core::ptr::null_mut(),
text: "ab".to_string(),
};
let mut rs = RichString::new();
CheckItem_display(&it, &mut rs);
let box_c = attr_of(ColorElements::CHECK_BOX);
let mark_c = attr_of(ColorElements::CHECK_MARK);
let text_c = attr_of(ColorElements::CHECK_TEXT);
assert_eq!(rendered(&rs), "[x] ab");
assert_eq!(rs.chptr[0].attr, box_c);
assert_eq!(rs.chptr[1].attr, mark_c);
for i in 2..=6 {
assert_eq!(rs.chptr[i].attr, box_c, "bracket/space attr at {i}");
}
assert_eq!(rs.chptr[7].attr, text_c);
assert_eq!(rs.chptr[8].attr, text_c);
}
#[test]
fn number_item_display_integer_pads_to_five() {
let mut it = number_item(42, 0, 0, 1000);
it.text = "Delay".to_string();
let mut rs = RichString::new();
NumberItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "[42] Delay");
assert_eq!(rs.chptr[1].attr, attr_of(ColorElements::CHECK_MARK));
assert_eq!(rs.chptr[2].attr, attr_of(ColorElements::CHECK_MARK));
assert_eq!(rs.chptr[4].attr, attr_of(ColorElements::CHECK_BOX));
assert_eq!(rs.chptr[7].attr, attr_of(ColorElements::CHECK_TEXT));
}
#[test]
fn number_item_display_no_padding_when_field_at_least_five() {
let mut it = number_item(12345, 0, 0, 100000);
it.text = "N".to_string();
let mut rs = RichString::new();
NumberItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "[12345]N");
}
#[test]
fn number_item_display_scaled_decimal() {
let mut it = number_item(150, -2, 0, 100000);
it.text = "Pct".to_string();
let mut rs = RichString::new();
NumberItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "[1.50] Pct");
}
#[test]
fn number_item_display_positive_scale_multiplies() {
let mut it = number_item(3, 1, 0, 1000);
it.text = "K".to_string();
let mut rs = RichString::new();
NumberItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "[30] K");
}
#[test]
fn number_item_display_editing_uses_edit_buffer() {
let mut it = number_item(999, 0, 0, 100000);
it.editing = true;
it.editBuffer = "7".to_string();
it.text = "X".to_string();
let mut rs = RichString::new();
NumberItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "[7] X");
assert_eq!(rs.chptr[1].attr, attr_of(ColorElements::CHECK_MARK));
}
#[test]
fn object_display_dispatches_for_each_kind() {
let t = TextItem {
text: "T".to_string(),
};
let mut rs = RichString::new();
Object::display(&t, &mut rs);
assert_eq!(rendered(&rs), "T");
let c = CheckItem {
value: false,
ref_: core::ptr::null_mut(),
text: "C".to_string(),
};
let mut rs2 = RichString::new();
Object::display(&c, &mut rs2);
assert_eq!(rendered(&rs2), "[ ] C");
let mut n = number_item(5, 0, 0, 100);
n.text = "N".to_string();
let mut rs3 = RichString::new();
Object::display(&n, &mut rs3);
assert_eq!(rendered(&rs3), "[5] N");
}
#[test]
fn number_item_new_by_val_clamps_initial_value() {
let it = NumberItem_newByVal("Delay", 999, 0, 0, 50);
assert_eq!(it.value, 50);
assert_eq!(it.scale, 0);
assert_eq!(it.min, 0);
assert_eq!(it.max, 50);
assert!(!it.editing);
assert_eq!(it.savedValue, 0);
assert!(it.editBuffer.is_empty());
assert_eq!(it.text, "Delay");
let lo = NumberItem_newByVal("N", -5, -2, 3, 100);
assert_eq!(lo.value, 3);
assert_eq!(lo.scale, -2);
let mid = NumberItem_newByVal("M", 20, 0, 0, 50);
assert_eq!(mid.value, 20);
}
#[test]
fn number_item_get_returns_value() {
let it = number_item(42, 0, 0, 100);
assert_eq!(NumberItem_get(&it), 42);
}
#[test]
fn number_item_decrease_clamps_at_min() {
let mut it = number_item(1, 0, 0, 10);
NumberItem_decrease(&mut it);
assert_eq!(it.value, 0);
NumberItem_decrease(&mut it);
assert_eq!(it.value, 0);
}
#[test]
fn number_item_increase_clamps_at_max() {
let mut it = number_item(9, 0, 0, 10);
NumberItem_increase(&mut it);
assert_eq!(it.value, 10);
NumberItem_increase(&mut it);
assert_eq!(it.value, 10);
}
#[test]
fn number_item_toggle_wraps_at_max() {
let mut it = number_item(0, 0, 0, 2);
NumberItem_toggle(&mut it);
assert_eq!(it.value, 1);
NumberItem_toggle(&mut it);
assert_eq!(it.value, 2);
NumberItem_toggle(&mut it);
assert_eq!(it.value, 0);
it.value = 5;
NumberItem_toggle(&mut it);
assert_eq!(it.value, 0);
}
#[test]
fn start_editing_saves_value_and_clears_buffer() {
let mut it = number_item(7, 0, 0, 100);
it.editBuffer = "stale".to_string();
NumberItem_startEditing(&mut it);
assert!(it.editing);
assert_eq!(it.savedValue, 7);
assert!(it.editBuffer.is_empty());
}
#[test]
fn start_editing_from_value_integer() {
let mut it = number_item(123, 0, 0, 1000);
NumberItem_startEditingFromValue(&mut it);
assert!(it.editing);
assert_eq!(it.savedValue, 123);
assert_eq!(it.editBuffer, "123");
}
#[test]
fn start_editing_from_value_scaled_decimal() {
let mut it = number_item(150, -2, 0, 10000);
NumberItem_startEditingFromValue(&mut it);
assert_eq!(it.editBuffer, "1.50");
}
#[test]
fn start_editing_from_value_truncates_to_max() {
let mut it = number_item(0, 0, 0, i32::MAX);
it.value = 2_000_000_000; NumberItem_startEditingFromValue(&mut it);
assert_eq!(it.editBuffer, "2000000000");
assert_eq!(it.editBuffer.len(), NUMBERITEM_EDIT_MAX);
}
#[test]
fn cancel_editing_clears() {
let mut it = number_item(3, 0, 0, 10);
it.editing = true;
it.editBuffer = "99".to_string();
NumberItem_cancelEditing(&mut it);
assert!(!it.editing);
assert!(it.editBuffer.is_empty());
}
#[test]
fn apply_editing_empty_buffer_returns_false() {
let mut it = number_item(5, 0, 0, 10);
it.editing = true;
assert!(!NumberItem_applyEditing(&mut it));
assert!(!it.editing);
assert_eq!(it.value, 5); }
#[test]
fn apply_editing_integer_commits_and_clamps() {
let mut it = number_item(0, 0, 0, 50);
it.editBuffer = "27".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(it.value, 27);
assert!(it.editBuffer.is_empty());
it.editBuffer = "999".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(it.value, 50);
}
#[test]
fn apply_editing_leading_numeric_prefix_like_strtol() {
let mut it = number_item(0, 0, 0, 100);
it.editBuffer = "12abc".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(it.value, 12);
}
#[test]
fn apply_editing_no_conversion_returns_false() {
let mut it = number_item(9, 0, 0, 100);
it.editBuffer = "abc".to_string();
assert!(!NumberItem_applyEditing(&mut it));
assert_eq!(it.value, 9); assert!(it.editBuffer.is_empty());
}
#[test]
fn apply_editing_scaled_decimal_round_trips() {
let mut it = number_item(0, -2, 0, 100000);
it.editBuffer = "1.50".to_string();
assert!(NumberItem_applyEditing(&mut it));
assert_eq!(it.value, 150);
}
#[test]
fn add_char_digits_and_bounds() {
let mut it = number_item(0, 0, 0, i32::MAX);
for d in b'0'..=b'9' {
assert!(NumberItem_addChar(&mut it, d as char));
}
assert_eq!(it.editBuffer, "0123456789");
assert_eq!(it.editBuffer.len(), NUMBERITEM_EDIT_MAX);
assert!(!NumberItem_addChar(&mut it, '0'));
assert_eq!(it.editBuffer.len(), NUMBERITEM_EDIT_MAX);
}
#[test]
fn add_char_rejects_non_digit() {
let mut it = number_item(0, 0, 0, 100);
assert!(!NumberItem_addChar(&mut it, 'a'));
assert!(!NumberItem_addChar(&mut it, '-'));
assert!(it.editBuffer.is_empty());
}
#[test]
fn add_char_dot_only_when_scale_negative() {
let mut pos = number_item(0, 0, 0, 100);
assert!(!NumberItem_addChar(&mut pos, '.'));
let mut neg = number_item(0, -2, 0, 100);
assert!(NumberItem_addChar(&mut neg, '1'));
assert!(NumberItem_addChar(&mut neg, '.'));
assert!(!NumberItem_addChar(&mut neg, '.'));
assert!(NumberItem_addChar(&mut neg, '5'));
assert_eq!(neg.editBuffer, "1.5");
}
#[test]
fn add_char_comma_normalized_to_dot() {
let mut it = number_item(0, -2, 0, 100);
assert!(NumberItem_addChar(&mut it, ','));
assert_eq!(it.editBuffer, ".");
assert!(!NumberItem_addChar(&mut it, ','));
}
#[test]
fn delete_char_removes_last_and_is_safe_when_empty() {
let mut it = number_item(0, 0, 0, 100);
it.editBuffer = "12".to_string();
NumberItem_deleteChar(&mut it);
assert_eq!(it.editBuffer, "1");
NumberItem_deleteChar(&mut it);
assert!(it.editBuffer.is_empty());
NumberItem_deleteChar(&mut it);
assert!(it.editBuffer.is_empty());
}
}