@@ -887,6 +887,177 @@ describe('<NumberField />', () => {
887
887
} ) ;
888
888
} ) ;
889
889
890
+ describe ( 'integration: exotic inputs and IME' , ( ) => {
891
+ it ( 'parses Persian digits and separators via change events' , async ( ) => {
892
+ const onValueChange = spy ( ) ;
893
+ function App ( ) {
894
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
895
+ return (
896
+ < NumberField
897
+ value = { value }
898
+ onValueChange = { ( v ) => {
899
+ onValueChange ( v ) ;
900
+ setValue ( v ) ;
901
+ } }
902
+ />
903
+ ) ;
904
+ }
905
+ await render ( < App /> ) ;
906
+
907
+ const input = screen . getByRole ( 'textbox' ) ;
908
+ // ۱۲٫۳۴ => 12.34
909
+ fireEvent . change ( input , { target : { value : '۱۲٫۳۴' } } ) ;
910
+
911
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
912
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( 12.34 ) ;
913
+ } ) ;
914
+
915
+ it ( 'parses Persian digits with Arabic group/decimal separators' , async ( ) => {
916
+ const onValueChange = spy ( ) ;
917
+ function App ( ) {
918
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
919
+ return (
920
+ < NumberField
921
+ value = { value }
922
+ onValueChange = { ( v ) => {
923
+ onValueChange ( v ) ;
924
+ setValue ( v ) ;
925
+ } }
926
+ />
927
+ ) ;
928
+ }
929
+ await render ( < App /> ) ;
930
+
931
+ const input = screen . getByRole ( 'textbox' ) ;
932
+ // ۱۲٬۳۴۵٫۶۷ => 12345.67
933
+ fireEvent . change ( input , { target : { value : '۱۲٬۳۴۵٫۶۷' } } ) ;
934
+
935
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
936
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( 12345.67 ) ;
937
+ } ) ;
938
+
939
+ it ( 'parses fullwidth digits and punctuation' , async ( ) => {
940
+ const onValueChange = spy ( ) ;
941
+ function App ( ) {
942
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
943
+ return (
944
+ < NumberField
945
+ value = { value }
946
+ onValueChange = { ( v ) => {
947
+ onValueChange ( v ) ;
948
+ setValue ( v ) ;
949
+ } }
950
+ />
951
+ ) ;
952
+ }
953
+
954
+ await render ( < App /> ) ;
955
+
956
+ const input = screen . getByRole ( 'textbox' ) ;
957
+
958
+ fireEvent . change ( input , { target : { value : '1,234.56' } } ) ;
959
+
960
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
961
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( 1234.56 ) ;
962
+ } ) ;
963
+
964
+ it ( 'parses percent and permille signs in exotic forms' , async ( ) => {
965
+ const onValueChange = spy ( ) ;
966
+ function App ( ) {
967
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
968
+ return (
969
+ < NumberField
970
+ value = { value }
971
+ onValueChange = { ( v ) => {
972
+ onValueChange ( v ) ;
973
+ setValue ( v ) ;
974
+ } }
975
+ />
976
+ ) ;
977
+ }
978
+
979
+ await render ( < App /> ) ;
980
+
981
+ const input = screen . getByRole ( 'textbox' ) ;
982
+ fireEvent . change ( input , { target : { value : '١٢٪' } } ) ;
983
+
984
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
985
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( 0.12 ) ;
986
+
987
+ // reset by typing again
988
+ fireEvent . change ( input , { target : { value : '12؉' } } ) ;
989
+ expect ( onValueChange . callCount ) . to . equal ( 2 ) ;
990
+ expect ( onValueChange . secondCall . args [ 0 ] ) . to . equal ( 0.012 ) ;
991
+ } ) ;
992
+
993
+ it ( 'parses trailing unicode minus and parentheses negatives' , async ( ) => {
994
+ const onValueChange = spy ( ) ;
995
+ function App ( ) {
996
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
997
+ return (
998
+ < NumberField
999
+ value = { value }
1000
+ onValueChange = { ( v ) => {
1001
+ onValueChange ( v ) ;
1002
+ setValue ( v ) ;
1003
+ } }
1004
+ />
1005
+ ) ;
1006
+ }
1007
+
1008
+ await render ( < App /> ) ;
1009
+
1010
+ const input = screen . getByRole ( 'textbox' ) ;
1011
+ fireEvent . change ( input , { target : { value : '1234−' } } ) ;
1012
+
1013
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
1014
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( - 1234 ) ;
1015
+
1016
+ fireEvent . change ( input , { target : { value : '(1,234.5)' } } ) ;
1017
+
1018
+ expect ( onValueChange . callCount ) . to . equal ( 2 ) ;
1019
+ expect ( onValueChange . secondCall . args [ 0 ] ) . to . equal ( - 1234.5 ) ;
1020
+ } ) ;
1021
+
1022
+ it ( 'collapses extra dots from mixed-locale inputs' , async ( ) => {
1023
+ const onValueChange = spy ( ) ;
1024
+ function App ( ) {
1025
+ const [ value , setValue ] = React . useState < number | null > ( null ) ;
1026
+ return (
1027
+ < NumberField
1028
+ value = { value }
1029
+ onValueChange = { ( v ) => {
1030
+ onValueChange ( v ) ;
1031
+ setValue ( v ) ;
1032
+ } }
1033
+ />
1034
+ ) ;
1035
+ }
1036
+
1037
+ await render ( < App /> ) ;
1038
+
1039
+ const input = screen . getByRole ( 'textbox' ) ;
1040
+ fireEvent . change ( input , { target : { value : '1.234.567.89' } } ) ;
1041
+
1042
+ expect ( onValueChange . callCount ) . to . equal ( 1 ) ;
1043
+ expect ( onValueChange . firstCall . args [ 0 ] ) . to . equal ( 1234567.89 ) ;
1044
+ } ) ;
1045
+
1046
+ it ( 'allows composition key events (IME) without preventing default' , async ( ) => {
1047
+ await render ( < NumberField /> ) ;
1048
+
1049
+ const input = screen . getByRole ( 'textbox' ) ;
1050
+
1051
+ await act ( async ( ) => input . focus ( ) ) ;
1052
+
1053
+ const preventDefaultSpy = spy ( ) ;
1054
+
1055
+ // 229 indicates a composition key event
1056
+ fireEvent . keyDown ( input , { which : 229 , preventDefault : preventDefaultSpy } ) ;
1057
+ expect ( preventDefaultSpy ) . to . have . property ( 'callCount' , 0 ) ;
1058
+ } ) ;
1059
+ } ) ;
1060
+
890
1061
describe . skipIf ( isJSDOM ) ( 'pasting' , ( ) => {
891
1062
it ( 'should allow pasting a valid number' , async ( ) => {
892
1063
await render ( < NumberField /> ) ;
0 commit comments