Properly set value and defaultValue for input and textarea (#6406)

* Have `defaultValue` reach DOM node for html input box for #4618

* Cleanup and bug fixes for merge.

(cherry picked from commit 4338c8db4b)
This commit is contained in:
Jim
2016-05-24 18:28:01 -07:00
committed by Paul O’Shannessy
parent a2f7b34f38
commit f98e6c1955
8 changed files with 371 additions and 119 deletions
@@ -149,8 +149,8 @@ var ReactDOMInput = {
var defaultValue = props.defaultValue;
inst._wrapperState = {
initialChecked: props.defaultChecked || false,
initialValue: defaultValue != null ? defaultValue : null,
initialChecked: props.checked != null ? props.checked : props.defaultChecked,
initialValue: props.value != null ? props.value : defaultValue,
listeners: null,
onChange: _handleChange.bind(inst),
};
@@ -166,13 +166,12 @@ var ReactDOMInput = {
if (__DEV__) {
warnIfValueIsNull(props);
var initialValue = inst._wrapperState.initialChecked || inst._wrapperState.initialValue;
var defaultValue = props.defaultChecked || props.defaultValue;
var controlled = props.checked !== undefined || props.value !== undefined;
var owner = inst._currentElement._owner;
if (
(initialValue || !inst._wrapperState.controlled) &&
!inst._wrapperState.controlled &&
controlled && !didWarnUncontrolledToControlled
) {
warning(
@@ -214,17 +213,45 @@ var ReactDOMInput = {
);
}
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
var value = LinkedValueUtils.getValue(props);
if (value != null) {
// Cast `value` to a string to ensure the value is set correctly. While
// browsers typically do this as necessary, jsdom doesn't.
DOMPropertyOperations.setValueForProperty(
ReactDOMComponentTree.getNodeFromInstance(inst),
'value',
'' + value
);
var newValue = '' + value;
// To avoid side effects (such as losing text selection), only set value if changed
if (newValue !== node.value) {
node.value = newValue;
}
} else {
if (props.value == null && props.defaultValue != null) {
node.defaultValue = '' + props.defaultValue;
}
if (props.checked == null && props.defaultChecked != null) {
node.defaultChecked = !!props.defaultChecked;
}
}
},
postMountWrapper: function(inst) {
// This is in postMount because we need access to the DOM node, which is not
// available until after the component has mounted.
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
node.value = node.value; // Detach value from defaultValue
// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
// this is needed to work around a chrome bug where setting defaultChecked
// will sometimes influence the value of checked (even after detachment).
// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
// We need to temporarily unset name to avoid disrupting radio button groups.
var name = node.name;
node.name = undefined;
node.defaultChecked = !node.defaultChecked;
node.defaultChecked = !node.defaultChecked;
node.name = name;
},
};
function _handleChange(event) {
@@ -12,7 +12,6 @@
'use strict';
var DisabledInputUtils = require('DisabledInputUtils');
var DOMPropertyOperations = require('DOMPropertyOperations');
var LinkedValueUtils = require('LinkedValueUtils');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactUpdates = require('ReactUpdates');
@@ -67,11 +66,14 @@ var ReactDOMTextarea = {
);
// Always set children to the same thing. In IE9, the selection range will
// get reset if `textContent` is mutated.
// get reset if `textContent` is mutated. We could add a check in setTextContent
// to only set the value if/when the value differs from the node value (which would
// completely solve this IE9 bug), but Sebastian+Ben seemed to like this solution.
// The value can be a boolean or object so that's why it's forced to be a string.
var hostProps = Object.assign({}, DisabledInputUtils.getHostProps(inst, props), {
defaultValue: undefined,
value: undefined,
children: inst._wrapperState.initialValue,
defaultValue: undefined,
children: '' + inst._wrapperState.initialValue,
onChange: inst._wrapperState.onChange,
});
@@ -110,41 +112,45 @@ var ReactDOMTextarea = {
warnIfValueIsNull(props);
}
var defaultValue = props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
var children = props.children;
if (children != null) {
if (__DEV__) {
warning(
false,
'Use the `defaultValue` or `value` props instead of setting ' +
'children on <textarea>.'
);
}
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.'
);
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.'
);
children = children[0];
}
defaultValue = '' + children;
}
if (defaultValue == null) {
defaultValue = '';
}
var value = LinkedValueUtils.getValue(props);
var initialValue = value;
// Only bother fetching default value if we're going to use it
if (value == null) {
var defaultValue = props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
var children = props.children;
if (children != null) {
if (__DEV__) {
warning(
false,
'Use the `defaultValue` or `value` props instead of setting ' +
'children on <textarea>.'
);
}
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.'
);
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.'
);
children = children[0];
}
defaultValue = '' + children;
}
if (defaultValue == null) {
defaultValue = '';
}
initialValue = defaultValue;
}
inst._wrapperState = {
// We save the initial value so that `ReactDOMComponent` doesn't update
// `textContent` (unnecessary since we update value).
// The initial value can be a boolean or object so that's why it's
// forced to be a string.
initialValue: '' + (value != null ? value : defaultValue),
initialValue: '' + initialValue,
listeners: null,
onChange: _handleChange.bind(inst),
};
@@ -157,16 +163,31 @@ var ReactDOMTextarea = {
warnIfValueIsNull(props);
}
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
var value = LinkedValueUtils.getValue(props);
if (value != null) {
// Cast `value` to a string to ensure the value is set correctly. While
// browsers typically do this as necessary, jsdom doesn't.
DOMPropertyOperations.setValueForProperty(
ReactDOMComponentTree.getNodeFromInstance(inst),
'value',
'' + value
);
var newValue = '' + value;
// To avoid side effects (such as losing text selection), only set value if changed
if (newValue !== node.value) {
node.value = newValue;
}
if (props.defaultValue == null) {
node.defaultValue = newValue;
}
}
if (props.defaultValue != null) {
node.defaultValue = props.defaultValue;
}
},
postMountWrapper: function(inst) {
// This is in postMount because we need access to the DOM node, which is not
// available until after the component has mounted.
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
node.value = node.value; // Detach value from defaultValue
},
};
@@ -18,6 +18,7 @@ describe('ReactDOMInput', function() {
var EventConstants;
var React;
var ReactDOM;
var ReactDOMServer;
var ReactDOMFeatureFlags;
var ReactLink;
var ReactTestUtils;
@@ -27,6 +28,7 @@ describe('ReactDOMInput', function() {
EventConstants = require('EventConstants');
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactLink = require('ReactLink');
ReactTestUtils = require('ReactTestUtils');
@@ -38,6 +40,7 @@ describe('ReactDOMInput', function() {
stub = ReactTestUtils.renderIntoDocument(stub);
var node = ReactDOM.findDOMNode(stub);
expect(node.getAttribute('value')).toBe('0');
expect(node.value).toBe('0');
});
@@ -57,6 +60,48 @@ describe('ReactDOMInput', function() {
expect(node.value).toBe('false');
});
it('should update `defaultValue` for uncontrolled input', function() {
var container = document.createElement('div');
var node = ReactDOM.render(<input type="text" defaultValue="0" />, container);
expect(node.value).toBe('0');
ReactDOM.render(<input type="text" defaultValue="1" />, container);
expect(node.value).toBe('0');
expect(node.defaultValue).toBe('1');
});
it('should take `defaultValue` when changing to uncontrolled input', function() {
var container = document.createElement('div');
var node = ReactDOM.render(<input type="text" value="0" readOnly="true" />, container);
expect(node.value).toBe('0');
ReactDOM.render(<input type="text" defaultValue="1" />, container);
expect(node.value).toBe('0');
});
it('should render defaultValue for SSR', function() {
var markup = ReactDOMServer.renderToString(<input type="text" defaultValue="1" />);
var div = document.createElement('div');
div.innerHTML = markup;
expect(div.firstChild.getAttribute('value')).toBe('1');
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
});
it('should render value for SSR', function() {
var element = <input type="text" value="1" onChange={function() {}} />;
var markup = ReactDOMServer.renderToString(element);
var div = document.createElement('div');
div.innerHTML = markup;
expect(div.firstChild.getAttribute('value')).toBe('1');
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
});
it('should display "foobar" for `defaultValue` of `objToString`', function() {
var objToString = {
toString: function() {
@@ -82,8 +127,7 @@ describe('ReactDOMInput', function() {
it('should allow setting `value` to `true`', function() {
var container = document.createElement('div');
var stub = <input type="text" value="yolo" onChange={emptyFunction} />;
stub = ReactDOM.render(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = ReactDOM.render(stub, container);
expect(node.value).toBe('yolo');
@@ -97,8 +141,7 @@ describe('ReactDOMInput', function() {
it('should allow setting `value` to `false`', function() {
var container = document.createElement('div');
var stub = <input type="text" value="yolo" onChange={emptyFunction} />;
stub = ReactDOM.render(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = ReactDOM.render(stub, container);
expect(node.value).toBe('yolo');
@@ -112,8 +155,7 @@ describe('ReactDOMInput', function() {
it('should allow setting `value` to `objToString`', function() {
var container = document.createElement('div');
var stub = <input type="text" value="foo" onChange={emptyFunction} />;
stub = ReactDOM.render(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = ReactDOM.render(stub, container);
expect(node.value).toBe('foo');
@@ -129,6 +171,29 @@ describe('ReactDOMInput', function() {
expect(node.value).toEqual('foobar');
});
it('should not incur unnecessary DOM mutations', function() {
var container = document.createElement('div');
ReactDOM.render(<input value="a" />, container);
var node = container.firstChild;
var nodeValue = 'a';
var nodeValueSetter = jest.genMockFn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
},
set: nodeValueSetter.mockImplementation(function(newValue) {
nodeValue = newValue;
}),
});
ReactDOM.render(<input value="a" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
ReactDOM.render(<input value="b"/>, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
});
it('should properly control a value of number `0`', function() {
var stub = <input type="text" value={0} onChange={emptyFunction} />;
stub = ReactTestUtils.renderIntoDocument(stub);
@@ -390,6 +455,13 @@ describe('ReactDOMInput', function() {
});
it('should update defaultValue to empty string', function() {
var container = document.createElement('div');
ReactDOM.render(<input type="text" defaultValue={'foo'} />, container);
ReactDOM.render(<input type="text" defaultValue={''} />, container);
expect(container.firstChild.defaultValue).toBe('');
});
it('should throw if both checkedLink and valueLink are provided', function() {
var node = document.createElement('div');
var link = new ReactLink(true, jest.fn());
@@ -609,6 +681,11 @@ describe('ReactDOMInput', function() {
'set data-reactroot',
'set type',
'set value',
'set value',
'set name',
'set checked',
'set checked',
'set name',
]);
});
@@ -16,6 +16,7 @@ var emptyFunction = require('emptyFunction');
describe('ReactDOMTextarea', function() {
var React;
var ReactDOM;
var ReactDOMServer;
var ReactLink;
var ReactTestUtils;
@@ -24,6 +25,7 @@ describe('ReactDOMTextarea', function() {
beforeEach(function() {
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
ReactLink = require('ReactLink');
ReactTestUtils = require('ReactTestUtils');
@@ -31,39 +33,41 @@ describe('ReactDOMTextarea', function() {
if (!container) {
container = document.createElement('div');
}
var stub = ReactDOM.render(component, container);
var node = ReactDOM.findDOMNode(stub);
var node = ReactDOM.render(component, container);
// Fixing jsdom's quirky behavior -- in reality, the parser should strip
// off the leading newline but we need to do it by hand here.
node.value = node.innerHTML.replace(/^\n/, '');
return stub;
node.defaultValue = node.innerHTML.replace(/^\n/, '');
return node;
};
});
it('should allow setting `defaultValue`', function() {
var container = document.createElement('div');
var stub = renderTextarea(<textarea defaultValue="giraffe" />, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(<textarea defaultValue="giraffe" />, container);
expect(node.value).toBe('giraffe');
// Changing `defaultValue` should do nothing.
stub = renderTextarea(<textarea defaultValue="gorilla" />, container);
renderTextarea(<textarea defaultValue="gorilla" />, container);
expect(node.value).toEqual('giraffe');
node.value = 'cat';
renderTextarea(<textarea defaultValue="monkey" />, container);
expect(node.value).toEqual('cat');
});
it('should display `defaultValue` of number 0', function() {
var stub = <textarea defaultValue={0} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
expect(node.value).toBe('0');
});
it('should display "false" for `defaultValue` of `false`', function() {
var stub = <textarea defaultValue={false} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
expect(node.value).toBe('false');
});
@@ -76,33 +80,44 @@ describe('ReactDOMTextarea', function() {
};
var stub = <textarea defaultValue={objToString} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
expect(node.value).toBe('foobar');
});
it('should set defaultValue', function() {
var container = document.createElement('div');
ReactDOM.render(<textarea defaultValue="foo" />, container);
ReactDOM.render(<textarea defaultValue="bar" />, container);
ReactDOM.render(<textarea defaultValue="noise" />, container);
expect(container.firstChild.defaultValue).toBe('noise');
});
it('should not render value as an attribute', function() {
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
expect(node.getAttribute('value')).toBe(null);
});
it('should display `value` of number 0', function() {
var stub = <textarea value={0} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
expect(node.value).toBe('0');
});
it('should update defaultValue to empty string', function() {
var container = document.createElement('div');
ReactDOM.render(<textarea defaultValue={'foo'} />, container);
ReactDOM.render(<textarea defaultValue={''} />, container);
expect(container.firstChild.defaultValue).toBe('');
});
it('should allow setting `value` to `giraffe`', function() {
var container = document.createElement('div');
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
stub = renderTextarea(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub, container);
expect(node.value).toBe('giraffe');
@@ -113,11 +128,27 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toEqual('gorilla');
});
it('should render defaultValue for SSR', function() {
var markup = ReactDOMServer.renderToString(<textarea defaultValue="1" />);
var div = document.createElement('div');
div.innerHTML = markup;
expect(div.firstChild.innerHTML).toBe('1');
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
});
it('should render value for SSR', function() {
var element = <textarea value="1" onChange={function() {}} />;
var markup = ReactDOMServer.renderToString(element);
var div = document.createElement('div');
div.innerHTML = markup;
expect(div.firstChild.innerHTML).toBe('1');
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
});
it('should allow setting `value` to `true`', function() {
var container = document.createElement('div');
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
stub = renderTextarea(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub, container);
expect(node.value).toBe('giraffe');
@@ -131,8 +162,7 @@ describe('ReactDOMTextarea', function() {
it('should allow setting `value` to `false`', function() {
var container = document.createElement('div');
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
stub = renderTextarea(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub, container);
expect(node.value).toBe('giraffe');
@@ -146,8 +176,7 @@ describe('ReactDOMTextarea', function() {
it('should allow setting `value` to `objToString`', function() {
var container = document.createElement('div');
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
stub = renderTextarea(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub, container);
expect(node.value).toBe('giraffe');
@@ -163,10 +192,58 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toEqual('foo');
});
it('should take updates to `defaultValue` for uncontrolled textarea', function() {
var container = document.createElement('div');
var node = ReactDOM.render(<textarea defaultValue="0" />, container);
expect(node.value).toBe('0');
ReactDOM.render(<textarea defaultValue="1" />, container);
expect(node.value).toBe('0');
});
it('should take updates to children in lieu of `defaultValue` for uncontrolled textarea', function() {
var container = document.createElement('div');
var node = ReactDOM.render(<textarea defaultValue="0" />, container);
expect(node.value).toBe('0');
spyOn(console, 'error'); // deprecation warning for `children` content
ReactDOM.render(<textarea>1</textarea>, container);
expect(node.value).toBe('0');
});
it('should not incur unnecessary DOM mutations', function() {
var container = document.createElement('div');
ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
var node = container.firstChild;
var nodeValue = 'a';
var nodeValueSetter = jest.genMockFn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
},
set: nodeValueSetter.mockImplementation(function(newValue) {
nodeValue = newValue;
}),
});
ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
ReactDOM.render(<textarea value="b" onChange={emptyFunction} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
});
it('should properly control a value of number `0`', function() {
var stub = <textarea value={0} onChange={emptyFunction} />;
stub = renderTextarea(stub);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub);
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
@@ -178,8 +255,7 @@ describe('ReactDOMTextarea', function() {
var container = document.createElement('div');
var stub = <textarea>giraffe</textarea>;
stub = renderTextarea(stub, container);
var node = ReactDOM.findDOMNode(stub);
var node = renderTextarea(stub, container);
expect(console.error.argsForCall.length).toBe(1);
expect(node.value).toBe('giraffe');
@@ -189,16 +265,44 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toEqual('giraffe');
});
it('should keep value when switching to uncontrolled element if not changed', function() {
var container = document.createElement('div');
var node = renderTextarea(<textarea value="kitten" onChange={emptyFunction} />, container);
expect(node.value).toBe('kitten');
ReactDOM.render(<textarea defaultValue="gorilla"></textarea>, container);
expect(node.value).toEqual('kitten');
});
it('should keep value when switching to uncontrolled element if changed', function() {
var container = document.createElement('div');
var node = renderTextarea(<textarea value="kitten" onChange={emptyFunction} />, container);
expect(node.value).toBe('kitten');
ReactDOM.render(<textarea value="puppies" onChange={emptyFunction}></textarea>, container);
expect(node.value).toBe('puppies');
ReactDOM.render(<textarea defaultValue="gorilla"></textarea>, container);
expect(node.value).toEqual('puppies');
});
it('should allow numbers as children', function() {
spyOn(console, 'error');
var node = ReactDOM.findDOMNode(renderTextarea(<textarea>{17}</textarea>));
var node = renderTextarea(<textarea>{17}</textarea>);
expect(console.error.argsForCall.length).toBe(1);
expect(node.value).toBe('17');
});
it('should allow booleans as children', function() {
spyOn(console, 'error');
var node = ReactDOM.findDOMNode(renderTextarea(<textarea>{false}</textarea>));
var node = renderTextarea(<textarea>{false}</textarea>);
expect(console.error.argsForCall.length).toBe(1);
expect(node.value).toBe('false');
});
@@ -210,7 +314,7 @@ describe('ReactDOMTextarea', function() {
return 'sharkswithlasers';
},
};
var node = ReactDOM.findDOMNode(renderTextarea(<textarea>{obj}</textarea>));
var node = renderTextarea(<textarea>{obj}</textarea>);
expect(console.error.argsForCall.length).toBe(1);
expect(node.value).toBe('sharkswithlasers');
});
@@ -228,7 +332,7 @@ describe('ReactDOMTextarea', function() {
var node;
expect(function() {
node = ReactDOM.findDOMNode(renderTextarea(<textarea><strong /></textarea>));
node = renderTextarea(<textarea><strong /></textarea>);
}).not.toThrow();
expect(node.value).toBe('[object Object]');
@@ -248,12 +352,12 @@ describe('ReactDOMTextarea', function() {
);
expect(ReactDOM.findDOMNode(instance).value).toBe('yolo');
expect(instance.value).toBe('yolo');
expect(link.value).toBe('yolo');
expect(link.requestChange.mock.calls.length).toBe(0);
ReactDOM.findDOMNode(instance).value = 'test';
ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(instance));
instance.value = 'test';
ReactTestUtils.Simulate.change(instance);
expect(link.requestChange.mock.calls.length).toBe(1);
expect(link.requestChange.mock.calls[0][0]).toEqual('test');
@@ -297,4 +401,5 @@ describe('ReactDOMTextarea', function() {
);
expect(console.error.argsForCall.length).toBe(1);
});
});
@@ -15,7 +15,6 @@ var DOMProperty = require('DOMProperty');
var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY;
var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE;
var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS;
var HAS_NUMERIC_VALUE = DOMProperty.injection.HAS_NUMERIC_VALUE;
var HAS_POSITIVE_NUMERIC_VALUE =
DOMProperty.injection.HAS_POSITIVE_NUMERIC_VALUE;
@@ -153,7 +152,7 @@ var HTMLDOMPropertyConfig = {
// Setting .type throws on non-<input> tags
type: 0,
useMap: 0,
value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
value: 0,
width: 0,
wmode: 0,
wrap: 0,
+23 -2
View File
@@ -240,6 +240,16 @@ function putListener() {
);
}
function inputPostMount() {
var inst = this;
ReactDOMInput.postMountWrapper(inst);
}
function textareaPostMount() {
var inst = this;
ReactDOMTextarea.postMountWrapper(inst);
}
function optionPostMount() {
var inst = this;
ReactDOMOption.postMountWrapper(inst);
@@ -634,10 +644,20 @@ ReactDOMComponent.Mixin = {
}
switch (this._tag) {
case 'button':
case 'input':
case 'select':
transaction.getReactMountReady().enqueue(
inputPostMount,
this
);
break;
case 'textarea':
transaction.getReactMountReady().enqueue(
textareaPostMount,
this
);
break;
case 'button':
case 'select':
if (props.autoFocus) {
transaction.getReactMountReady().enqueue(
AutoFocusUtils.focusDOMComponent,
@@ -650,6 +670,7 @@ ReactDOMComponent.Mixin = {
optionPostMount,
this
);
break;
}
return mountImage;
@@ -496,30 +496,32 @@ describe('ReactDOMComponent', function() {
ReactDOM.render(<div value="" />, container);
var node = container.firstChild;
var nodeValue = ''; // node.value always returns undefined
var nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
},
set: nodeValueSetter.mockImplementation(function(newValue) {
nodeValue = newValue;
}),
});
function renderWithValueAndExpect(value, expected) {
ReactDOM.render(<div value={value} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(expected);
}
var nodeValueSetter = jest.genMockFn();
renderWithValueAndExpect(undefined, 0);
renderWithValueAndExpect('', 0);
renderWithValueAndExpect('foo', 1);
renderWithValueAndExpect('foo', 1);
renderWithValueAndExpect(undefined, 2);
renderWithValueAndExpect(null, 2);
renderWithValueAndExpect('', 2);
renderWithValueAndExpect(undefined, 2);
var oldSetAttribute = node.setAttribute.bind(node);
node.setAttribute = function(key, value) {
oldSetAttribute(key, value);
nodeValueSetter(key, value);
};
ReactDOM.render(<div value="foo" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
ReactDOM.render(<div value="foo" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
ReactDOM.render(<div />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
ReactDOM.render(<div value={null} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
ReactDOM.render(<div value="" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(2);
ReactDOM.render(<div />, container);
expect(nodeValueSetter.mock.calls.length).toBe(2);
});
it('should not incur unnecessary DOM mutations for boolean properties', function() {
@@ -108,7 +108,7 @@ describe('ReactStatelessComponent', function() {
}).toThrow();
expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toContain(
'NotAComponent(...): A valid React element (or null) must be returned. '+
'NotAComponent(...): A valid React element (or null) must be returned. ' +
'You may have returned undefined, an array or some other invalid object.'
);
});