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