mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user