quantity.rs (4486B)
1 mod common; 2 3 use radroots_core::{Decimal, Quantity, Unit, quantity::Error}; 4 5 #[test] 6 fn zero_and_checked_constructor_preserve_invariants() { 7 let zero = Quantity::zero(Unit::MassKg); 8 assert!(zero.is_zero()); 9 assert_eq!(zero.amount(), Decimal::ZERO); 10 assert_eq!(zero.unit(), Unit::MassKg); 11 assert_eq!(zero.canonical_unit(), Unit::MassG); 12 assert!(!zero.is_canonical()); 13 14 assert_eq!( 15 Quantity::try_new(common::dec("-1"), Unit::Each), 16 Err(Error::NegativeAmount) 17 ); 18 } 19 20 #[test] 21 fn label_helpers_set_replace_and_clear() { 22 let quantity = common::qty("1", Unit::Each).with_label("box"); 23 assert_eq!(quantity.label(), Some("box")); 24 let quantity = quantity.with_optional_label(Some("case")); 25 assert_eq!(quantity.label(), Some("case")); 26 let quantity = quantity.with_optional_label::<&str>(None); 27 assert_eq!(quantity.label(), None); 28 assert_eq!(quantity.with_label("bag").clear_label().label(), None); 29 } 30 31 #[test] 32 fn checked_addition_and_subtraction_require_matching_units() { 33 let first = common::qty("5", Unit::Each).with_label("lhs"); 34 let second = common::qty("2", Unit::Each); 35 let kilograms = common::qty("1", Unit::MassKg); 36 37 let sum = first.try_add(&second).unwrap(); 38 assert_eq!(sum.amount(), common::dec("7")); 39 assert_eq!(sum.label(), Some("lhs")); 40 let difference = first.try_sub(&second).unwrap(); 41 assert_eq!(difference.amount(), common::dec("3")); 42 assert_eq!(difference.label(), Some("lhs")); 43 44 assert_eq!(first.try_add(&kilograms), Err(Error::UnitMismatch)); 45 assert_eq!(first.try_sub(&kilograms), Err(Error::UnitMismatch)); 46 assert!(first.checked_add(&kilograms).is_none()); 47 assert!(first.checked_sub(&kilograms).is_none()); 48 assert_eq!(second.try_sub(&first), Err(Error::NegativeAmount)); 49 } 50 51 #[test] 52 fn checked_scaling_reports_negative_overflow_and_zero_division() { 53 let quantity = common::qty("2", Unit::Each).with_label("unit"); 54 let scaled = quantity.checked_mul_decimal(common::dec("2.5")).unwrap(); 55 assert_eq!(scaled.amount(), common::dec("5")); 56 assert_eq!(scaled.unit(), Unit::Each); 57 assert_eq!(scaled.label(), Some("unit")); 58 59 let divided = quantity.checked_div_decimal(common::dec("2")).unwrap(); 60 assert_eq!(divided.amount(), common::dec("1")); 61 assert_eq!( 62 quantity.checked_mul_decimal(common::dec("-1")), 63 Err(Error::NegativeAmount) 64 ); 65 assert_eq!( 66 quantity.checked_div_decimal(Decimal::ZERO), 67 Err(Error::DivisionByZero) 68 ); 69 70 let max = Quantity::try_new(Decimal::MAX, Unit::Each).unwrap(); 71 assert_eq!(max.try_add(&quantity), Err(Error::ArithmeticOverflow)); 72 } 73 74 #[test] 75 fn conversion_is_checked_and_preserves_labels() { 76 let kilograms = common::qty("1", Unit::MassKg).with_label("bag"); 77 let grams = kilograms.try_convert_to(Unit::MassG).unwrap(); 78 assert_eq!(grams.amount(), common::dec("1000")); 79 assert_eq!(grams.unit(), Unit::MassG); 80 assert_eq!(grams.label(), Some("bag")); 81 assert!(grams.is_canonical()); 82 83 let canonical = common::qty("2", Unit::VolumeL).to_canonical().unwrap(); 84 assert_eq!(canonical.unit(), Unit::VolumeMl); 85 assert_eq!(canonical.amount(), common::dec("2000")); 86 87 assert_eq!( 88 common::qty("1", Unit::Each).try_convert_to(Unit::MassG), 89 Err(radroots_core::unit::ConvertError::NotConvertibleUnits { 90 from: Unit::Each, 91 to: Unit::MassG, 92 }) 93 ); 94 } 95 96 #[test] 97 fn display_and_error_messages_are_stable() { 98 assert_eq!( 99 common::qty("1.5", Unit::Each).with_label("bag").to_string(), 100 "1.5 each (bag)" 101 ); 102 assert_eq!(common::qty("1.5", Unit::Each).to_string(), "1.5 each"); 103 assert_eq!( 104 Error::NegativeAmount.to_string(), 105 "quantity amount must be ≥ 0" 106 ); 107 assert_eq!(Error::UnitMismatch.to_string(), "quantity unit mismatch"); 108 assert_eq!( 109 Error::ArithmeticOverflow.to_string(), 110 "quantity arithmetic overflow" 111 ); 112 assert_eq!( 113 Error::DivisionByZero.to_string(), 114 "quantity division by zero" 115 ); 116 } 117 118 #[test] 119 fn display_propagates_formatter_errors() { 120 use core::fmt::{self, Write}; 121 122 struct FailWriter; 123 impl Write for FailWriter { 124 fn write_str(&mut self, _: &str) -> fmt::Result { 125 Err(fmt::Error) 126 } 127 } 128 129 let quantity = common::qty("1.5", Unit::Each).with_label("bag"); 130 assert!(fmt::write(&mut FailWriter, format_args!("{quantity}")).is_err()); 131 }