@@ -261,8 +261,10 @@ fn apply_grouping(s: &str) -> String {
261261 grouped. push_str ( & integer[ ..first_group] ) ;
262262 for chunk in integer. as_bytes ( ) [ first_group..] . chunks ( 3 ) {
263263 grouped. push_str ( grouping_separator) ;
264- // SAFETY: integer is known to be valid UTF-8 ASCII digits
265- grouped. push_str ( std:: str:: from_utf8 ( chunk) . unwrap ( ) ) ;
264+ // integer is valid UTF-8 ASCII digits, so this cannot fail
265+ if let Ok ( s) = std:: str:: from_utf8 ( chunk) {
266+ grouped. push_str ( s) ;
267+ }
266268 }
267269
268270 if !fraction. is_empty ( ) {
@@ -994,4 +996,140 @@ mod tests {
994996 assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
995997 assert_eq ! ( value, 5.0 ) ;
996998 }
999+
1000+ #[ test]
1001+ fn test_detailed_error_message_empty ( ) {
1002+ let result = detailed_error_message ( "" , Unit :: Auto , "" ) ;
1003+ assert ! ( result. is_some( ) ) ;
1004+ }
1005+
1006+ #[ test]
1007+ fn test_detailed_error_message_valid_number ( ) {
1008+ // A plain valid number should return None (no error)
1009+ assert ! ( detailed_error_message( "123" , Unit :: Auto , "" ) . is_none( ) ) ;
1010+ assert ! ( detailed_error_message( "5K" , Unit :: Auto , "" ) . is_none( ) ) ;
1011+ assert ! ( detailed_error_message( "-3.5M" , Unit :: Auto , "" ) . is_none( ) ) ;
1012+ }
1013+
1014+ #[ test]
1015+ fn test_detailed_error_message_trailing_garbage ( ) {
1016+ // Number with suffix followed by extra chars
1017+ let result = detailed_error_message ( "5Kx" , Unit :: Auto , "" ) . unwrap ( ) ;
1018+ assert ! (
1019+ result. contains( "numfmt-error-invalid-specific-suffix" )
1020+ || result. contains( "invalid suffix" )
1021+ ) ;
1022+ }
1023+
1024+ #[ test]
1025+ fn test_detailed_error_message_dot_only ( ) {
1026+ let result = detailed_error_message ( "." , Unit :: Auto , "" ) . unwrap ( ) ;
1027+ assert ! (
1028+ result. contains( "numfmt-error-invalid-suffix" ) || result. contains( "invalid suffix" )
1029+ ) ;
1030+ }
1031+
1032+ #[ test]
1033+ fn test_detailed_error_message_trailing_dot ( ) {
1034+ let result = detailed_error_message ( "5." , Unit :: Auto , "" ) . unwrap ( ) ;
1035+ assert ! (
1036+ result. contains( "numfmt-error-invalid-number" ) || result. contains( "invalid number" )
1037+ ) ;
1038+ }
1039+
1040+ #[ test]
1041+ fn test_detailed_error_message_unit_separator ( ) {
1042+ // With unit separator, "5 K" is valid
1043+ assert ! ( detailed_error_message( "5 K" , Unit :: Auto , " " ) . is_none( ) ) ;
1044+
1045+ // "5 Kx" should report trailing garbage after the suffix
1046+ let result = detailed_error_message ( "5 Kx" , Unit :: Auto , " " ) ;
1047+ assert ! ( result. is_some( ) ) ;
1048+ }
1049+
1050+ #[ test]
1051+ fn test_parse_number_part_valid ( ) {
1052+ assert_eq ! ( parse_number_part( "42" , "42" ) . unwrap( ) , 42.0 ) ;
1053+ assert_eq ! ( parse_number_part( "-3.5" , "-3.5" ) . unwrap( ) , -3.5 ) ;
1054+ assert_eq ! ( parse_number_part( "0" , "0" ) . unwrap( ) , 0.0 ) ;
1055+ }
1056+
1057+ #[ test]
1058+ fn test_parse_number_part_trailing_dot ( ) {
1059+ assert ! ( parse_number_part( "5." , "5." ) . is_err( ) ) ;
1060+ }
1061+
1062+ #[ test]
1063+ fn test_parse_number_part_non_numeric ( ) {
1064+ assert ! ( parse_number_part( "abc" , "abc" ) . is_err( ) ) ;
1065+ assert ! ( parse_number_part( "" , "" ) . is_err( ) ) ;
1066+ }
1067+
1068+ #[ test]
1069+ fn test_apply_grouping_short_numbers ( ) {
1070+ // Numbers with fewer than 4 digits should be unchanged
1071+ assert_eq ! ( apply_grouping( "0" ) , "0" ) ;
1072+ assert_eq ! ( apply_grouping( "999" ) , "999" ) ;
1073+ assert_eq ! ( apply_grouping( "-99" ) , "-99" ) ;
1074+ }
1075+
1076+ #[ test]
1077+ fn test_apply_grouping_with_fraction ( ) {
1078+ // Fraction part should not be grouped
1079+ let result = apply_grouping ( "1234.567" ) ;
1080+ // Depending on locale, separator may or may not be present
1081+ assert ! ( result. contains( "567" ) ) ;
1082+ assert ! ( result. contains( '.' ) ) ;
1083+ }
1084+
1085+ #[ test]
1086+ fn test_apply_grouping_negative ( ) {
1087+ let result = apply_grouping ( "-1234" ) ;
1088+ assert ! ( result. starts_with( '-' ) ) ;
1089+ }
1090+
1091+ #[ test]
1092+ fn test_apply_grouping_large_numbers ( ) {
1093+ // These tests verify grouping structure; actual separator depends on locale
1094+ let result = apply_grouping ( "1000000" ) ;
1095+ // Should have separators inserted (length grows if separator is non-empty)
1096+ assert ! ( result. len( ) >= 7 ) ;
1097+
1098+ let result = apply_grouping ( "1234567890" ) ;
1099+ assert ! ( result. len( ) >= 10 ) ;
1100+
1101+ let result = apply_grouping ( "-9999999999999" ) ;
1102+ assert ! ( result. starts_with( '-' ) ) ;
1103+ assert ! ( result. len( ) >= 13 ) ;
1104+ }
1105+
1106+ #[ test]
1107+ fn test_apply_grouping_tiny_fraction ( ) {
1108+ // Small decimal: integer part < 4 digits, so no grouping
1109+ assert_eq ! ( apply_grouping( "0.000001" ) , "0.000001" ) ;
1110+ assert_eq ! ( apply_grouping( "1.23456789" ) , "1.23456789" ) ;
1111+ }
1112+
1113+ #[ test]
1114+ fn test_apply_grouping_exactly_four_digits ( ) {
1115+ let result = apply_grouping ( "1000" ) ;
1116+ // Should be grouped (4 digits)
1117+ assert ! ( result. len( ) >= 4 ) ;
1118+ }
1119+
1120+ #[ test]
1121+ fn test_parse_number_part_large_and_tiny ( ) {
1122+ assert_eq ! (
1123+ parse_number_part( "999999999999" , "999999999999" ) . unwrap( ) ,
1124+ 999_999_999_999.0
1125+ ) ;
1126+ assert_eq ! (
1127+ parse_number_part( "0.000000001" , "0.000000001" ) . unwrap( ) ,
1128+ 0.000_000_001
1129+ ) ;
1130+ assert_eq ! (
1131+ parse_number_part( "-99999999" , "-99999999" ) . unwrap( ) ,
1132+ -99_999_999.0
1133+ ) ;
1134+ }
9971135}
0 commit comments