@@ -519,6 +519,71 @@ async def test_above_cutoff_multi_digit_fraction(self):
519519 self .assertIn ("point" , result )
520520 self .assertIn ("7 5" , result )
521521
522+ async def test_trailing_fractional_zero_is_spoken (self ):
523+ # Every written digit is spoken, trailing zeros included -- the above-cutoff
524+ # branch reads fractions the same way.
525+ transform = expand_numbers (digit_cutoff = 2025 )
526+ cases = {
527+ "1.0" : "one point zero" ,
528+ "1.00" : "one point zero zero" ,
529+ "2.0" : "two point zero" ,
530+ "0.50" : "zero point five zero" ,
531+ "1.10" : "one point one zero" ,
532+ "10.0" : "ten point zero" ,
533+ }
534+ for text , expected in cases .items ():
535+ with self .subTest (text = text ):
536+ self .assertEqual (await transform (text , "*" ), expected )
537+
538+ async def test_fractions_without_trailing_zeros_unchanged (self ):
539+ # The digit-by-digit reading covers every decimal, not just the ones with a
540+ # trailing zero.
541+ transform = expand_numbers (digit_cutoff = None )
542+ cases = {
543+ "3.5" : "three point five" ,
544+ "1.05" : "one point zero five" ,
545+ "1.25" : "one point two five" ,
546+ "12.345" : "twelve point three four five" ,
547+ "5.0001" : "five point zero zero zero one" ,
548+ }
549+ for text , expected in cases .items ():
550+ with self .subTest (text = text ):
551+ self .assertEqual (await transform (text , "*" ), expected )
552+
553+
554+ class TestUnitsAndNumbersComposition (unittest .IsolatedAsyncioTestCase ):
555+ """``expand_units`` and ``expand_numbers`` have to agree on decimal quantities.
556+
557+ ``expand_units`` keeps the plural for a decimal such as "1.0" because it reads as
558+ "one point zero" in speech, and ``expand_numbers`` runs after it in the default
559+ ``VoiceFormatter`` order. Both have to spell the decimal out the same way, or the
560+ composed output is ungrammatical: "one kilometers".
561+ """
562+
563+ async def test_decimal_one_agrees_with_plural_unit (self ):
564+ formatter = VoiceFormatter (expand_numbers = True )
565+ cases = {
566+ "1.0km left" : "one point zero kilometers left" ,
567+ "1.0kg of flour" : "one point zero kilograms of flour" ,
568+ "1.0 mi away" : "one point zero miles away" ,
569+ "1.00km left" : "one point zero zero kilometers left" ,
570+ }
571+ for text , expected in cases .items ():
572+ with self .subTest (text = text ):
573+ self .assertEqual (await formatter (text , "*" ), expected )
574+
575+ async def test_singular_and_plural_quantities_still_agree (self ):
576+ formatter = VoiceFormatter (expand_numbers = True )
577+ cases = {
578+ "1km left" : "one kilometer left" ,
579+ "2.0km left" : "two point zero kilometers left" ,
580+ "1.5km left" : "one point five kilometers left" ,
581+ "1.0hz tone" : "one point zero hertz tone" ,
582+ }
583+ for text , expected in cases .items ():
584+ with self .subTest (text = text ):
585+ self .assertEqual (await formatter (text , "*" ), expected )
586+
522587
523588if __name__ == "__main__" :
524589 unittest .main ()
0 commit comments