@@ -1043,6 +1043,63 @@ describe('ReactDOMComponent', () => {
1043
1043
expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 2 ) ;
1044
1044
} ) ;
1045
1045
1046
+ it ( 'should not incur unnecessary DOM mutations for controlled string properties' , ( ) => {
1047
+ function onChange ( ) { }
1048
+ const container = document . createElement ( 'div' ) ;
1049
+ ReactDOM . render ( < input value = "" onChange = { onChange } /> , container ) ;
1050
+
1051
+ const node = container . firstChild ;
1052
+
1053
+ let nodeValue = '' ;
1054
+ const nodeValueSetter = jest . fn ( ) ;
1055
+ Object . defineProperty ( node , 'value' , {
1056
+ get : function ( ) {
1057
+ return nodeValue ;
1058
+ } ,
1059
+ set : nodeValueSetter . mockImplementation ( function ( newValue ) {
1060
+ nodeValue = newValue ;
1061
+ } ) ,
1062
+ } ) ;
1063
+
1064
+ ReactDOM . render ( < input value = "foo" onChange = { onChange } /> , container ) ;
1065
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 1 ) ;
1066
+
1067
+ ReactDOM . render (
1068
+ < input value = "foo" data-unrelated = { true } onChange = { onChange } /> ,
1069
+ container ,
1070
+ ) ;
1071
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 1 ) ;
1072
+
1073
+ expect ( ( ) => {
1074
+ ReactDOM . render ( < input onChange = { onChange } /> , container ) ;
1075
+ } ) . toErrorDev (
1076
+ 'A component is changing a controlled input to be uncontrolled. This is likely caused by ' +
1077
+ 'the value changing from a defined to undefined, which should not happen. Decide between ' +
1078
+ 'using a controlled or uncontrolled input element for the lifetime of the component.' ,
1079
+ ) ;
1080
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 1 ) ;
1081
+
1082
+ expect ( ( ) => {
1083
+ ReactDOM . render ( < input value = { null } onChange = { onChange } /> , container ) ;
1084
+ } ) . toErrorDev (
1085
+ 'value` prop on `input` should not be null. Consider using an empty string to clear the ' +
1086
+ 'component or `undefined` for uncontrolled components.' ,
1087
+ ) ;
1088
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 1 ) ;
1089
+
1090
+ expect ( ( ) => {
1091
+ ReactDOM . render ( < input value = "" onChange = { onChange } /> , container ) ;
1092
+ } ) . toErrorDev (
1093
+ ' A component is changing an uncontrolled input to be controlled. This is likely caused by ' +
1094
+ 'the value changing from undefined to a defined value, which should not happen. Decide between ' +
1095
+ 'using a controlled or uncontrolled input element for the lifetime of the component.' ,
1096
+ ) ;
1097
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 2 ) ;
1098
+
1099
+ ReactDOM . render ( < input onChange = { onChange } /> , container ) ;
1100
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 2 ) ;
1101
+ } ) ;
1102
+
1046
1103
it ( 'should not incur unnecessary DOM mutations for boolean properties' , ( ) => {
1047
1104
const container = document . createElement ( 'div' ) ;
1048
1105
function onChange ( ) {
@@ -1066,7 +1123,12 @@ describe('ReactDOMComponent', () => {
1066
1123
} ) ;
1067
1124
1068
1125
ReactDOM . render (
1069
- < input type = "checkbox" onChange = { onChange } checked = { true } /> ,
1126
+ < input
1127
+ type = "checkbox"
1128
+ onChange = { onChange }
1129
+ checked = { true }
1130
+ data-unrelated = { true }
1131
+ /> ,
1070
1132
container ,
1071
1133
) ;
1072
1134
expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 0 ) ;
@@ -1094,15 +1156,13 @@ describe('ReactDOMComponent', () => {
1094
1156
'using a controlled or uncontrolled input element for the lifetime of the component.' ,
1095
1157
) ;
1096
1158
1097
- // TODO: Non-null values are updated twice on inputs. This is should ideally be fixed.
1098
- expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 3 ) ;
1159
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 2 ) ;
1099
1160
1100
1161
ReactDOM . render (
1101
1162
< input type = "checkbox" onChange = { onChange } checked = { true } /> ,
1102
1163
container ,
1103
1164
) ;
1104
- // TODO: Non-null values are updated twice on inputs. This is should ideally be fixed.
1105
- expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 5 ) ;
1165
+ expect ( nodeValueSetter ) . toHaveBeenCalledTimes ( 3 ) ;
1106
1166
} ) ;
1107
1167
1108
1168
it ( 'should ignore attribute list for elements with the "is" attribute' , ( ) => {
0 commit comments