diff --git a/src/__tests__/DOMComponent.js b/src/__tests__/DOMComponent.js deleted file mode 100644 index 0db4a8d..0000000 --- a/src/__tests__/DOMComponent.js +++ /dev/null @@ -1,1729 +0,0 @@ -import React from 'react'; -import { render, findDOMNode } from 'react-dom-lite'; -import { renderIntoDetachedNode as renderIntoDocument } from 'react-dom-lite/test-utils'; -describe('ReactDOMInput', () => { - let setUntrackedValue; - - function normalizeCodeLocInfo(str) { - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); - } - - function dispatchEventOnNode(node, type) { - node.dispatchEvent(new Event(type, { bubbles: true, cancelable: true })); - } - - beforeEach(() => { - setUntrackedValue = Object.getOwnPropertyDescriptor( - HTMLInputElement.prototype, - 'value', - ).set; - }); - - it('should properly control a value even if no event listener exists', () => { - const container = document.createElement('div'); - const stub = render(, container); - - document.body.appendChild(container); - - const node = findDOMNode(stub); - - setUntrackedValue.call(node, 'giraffe'); - - // This must use the native event dispatching. If we simulate, we will - // bypass the lazy event attachment system so we won't actually test this. - dispatchEventOnNode(node, 'change'); - - expect(node.value).toBe('lion'); - - document.body.removeChild(container); - }); - - it('should control a value in reentrant events', () => { - class ControlledInputs extends React.Component { - state = { value: 'lion' }; - a = null; - b = null; - switchedFocus = false; - change(newValue) { - this.setState({ value: newValue }); - // Calling focus here will blur the text box which causes a native - // change event. Ideally we shouldn't have to fire this ourselves. - // Don't remove unless you've verified the fix in #8240 is still covered. - dispatchEventOnNode(this.a, 'change'); - this.b.focus(); - } - blur(currentValue) { - this.switchedFocus = true; - // currentValue should be 'giraffe' here because we should not have - // restored it on the target yet. - this.setState({ value: currentValue }); - } - render() { - return ( -
- (this.a = n)} - value={this.state.value} - onChange={e => this.change(e.target.value)} - onBlur={e => this.blur(e.target.value)} - /> - (this.b = n)} /> -
- ); - } - } - - const container = document.createElement('div'); - const instance = render(, container); - - // We need it to be in the body to test native event dispatching. - document.body.appendChild(container); - - // Focus the field so we can later blur it. - // Don't remove unless you've verified the fix in #8240 is still covered. - instance.a.focus(); - setUntrackedValue.call(instance.a, 'giraffe'); - // This must use the native event dispatching. If we simulate, we will - // bypass the lazy event attachment system so we won't actually test this. - dispatchEventOnNode(instance.a, 'change'); - dispatchEventOnNode(instance.a, 'blur'); - - expect(instance.a.value).toBe('giraffe'); - expect(instance.switchedFocus).toBe(true); - - document.body.removeChild(container); - }); - - it('should control values in reentrant events with different targets', () => { - class ControlledInputs extends React.Component { - state = { value: 'lion' }; - a = null; - b = null; - change(newValue) { - // This click will change the checkbox's value to false. Then it will - // invoke an inner change event. When we finally, flush, we need to - // reset the checkbox's value to true since that is its controlled - // value. - this.b.click(); - } - render() { - return ( -
- (this.a = n)} - value="lion" - onChange={e => this.change(e.target.value)} - /> - (this.b = n)} - checked={true} - onChange={() => {}} - /> -
- ); - } - } - - const container = document.createElement('div'); - const instance = render(, container); - - // We need it to be in the body to test native event dispatching. - document.body.appendChild(container); - - setUntrackedValue.call(instance.a, 'giraffe'); - // This must use the native event dispatching. If we simulate, we will - // bypass the lazy event attachment system so we won't actually test this. - dispatchEventOnNode(instance.a, 'input'); - - expect(instance.a.value).toBe('lion'); - expect(instance.b.checked).toBe(true); - - document.body.removeChild(container); - }); - - describe('switching text inputs between numeric and string numbers', () => { - it('does change the number 2 to "2.0" with no change handler', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - node.value = '2.0'; - - ReactTestUtils.Simulate.change(stub); - - expect(node.getAttribute('value')).toBe('2'); - expect(node.value).toBe('2'); - }); - - it('does change the string "2" to "2.0" with no change handler', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - node.value = '2.0'; - - ReactTestUtils.Simulate.change(stub); - - expect(node.getAttribute('value')).toBe('2'); - expect(node.value).toBe('2'); - }); - - it('changes the number 2 to "2.0" using a change handler', () => { - class Stub extends React.Component { - state = { - value: 2, - }; - onChange = event => { - this.setState({ value: event.target.value }); - }; - render() { - const { value } = this.state; - - return ; - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(stub); - - node.value = '2.0'; - - ReactTestUtils.Simulate.change(node); - - expect(node.getAttribute('value')).toBe('2.0'); - expect(node.value).toBe('2.0'); - }); - }); - - it('does change the string ".98" to "0.98" with no change handler', () => { - spyOnDev(console, 'error'); - class Stub extends React.Component { - state = { - value: '.98', - }; - render() { - return ; - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(stub); - stub.setState({ value: '0.98' }); - - expect(node.value).toEqual('0.98'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'You provided a `value` prop to a form field ' + - 'without an `onChange` handler.', - ); - } - }); - - it('performs a state change from "" to 0', () => { - class Stub extends React.Component { - state = { - value: '', - }; - render() { - return ; - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(stub); - stub.setState({ value: 0 }); - - expect(node.value).toEqual('0'); - }); - - it('distinguishes precision for extra zeroes in string number values', () => { - spyOnDev(console, 'error'); - class Stub extends React.Component { - state = { - value: '3.0000', - }; - render() { - return ; - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(stub); - stub.setState({ value: '3' }); - - expect(node.value).toEqual('3'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'You provided a `value` prop to a form field ' + - 'without an `onChange` handler.', - ); - } - }); - - it('should display `defaultValue` of number 0', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - expect(node.getAttribute('value')).toBe('0'); - expect(node.value).toBe('0'); - }); - - it('only assigns defaultValue if it changes', () => { - class Test extends React.Component { - render() { - return ; - } - } - - const component = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(component); - - Object.defineProperty(node, 'defaultValue', { - get() { - return '0'; - }, - set(value) { - throw new Error( - `defaultValue was assigned ${value}, but it did not change!`, - ); - }, - }); - - component.forceUpdate(); - }); - - it('should display "true" for `defaultValue` of `true`', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - expect(node.value).toBe('true'); - }); - - it('should display "false" for `defaultValue` of `false`', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - expect(node.value).toBe('false'); - }); - - it('should update `defaultValue` for uncontrolled input', () => { - const container = document.createElement('div'); - - const node = render(, container); - - expect(node.value).toBe('0'); - - render(, container); - - expect(node.value).toBe('0'); - expect(node.defaultValue).toBe('1'); - }); - - it('should update `defaultValue` for uncontrolled date/time input', () => { - const container = document.createElement('div'); - - const node = render( - , - container, - ); - - expect(node.value).toBe('1980-01-01'); - - render(, container); - - expect(node.value).toBe('1980-01-01'); - expect(node.defaultValue).toBe('2000-01-01'); - - render(, container); - }); - - it('should take `defaultValue` when changing to uncontrolled input', () => { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - const node = render( - , - container, - ); - expect(node.value).toBe('0'); - render(, container); - expect(node.value).toBe('0'); - - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type ' + - 'text to be uncontrolled.', - ); - } - }); - - it('should render defaultValue for SSR', () => { - const markup = ReactDOMServer.renderToString( - , - ); - const 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', () => { - const element = {}} />; - const markup = ReactDOMServer.renderToString(element); - const div = document.createElement('div'); - div.innerHTML = markup; - expect(div.firstChild.getAttribute('value')).toBe('1'); - expect(div.firstChild.getAttribute('defaultValue')).toBe(null); - }); - - it('should render name attribute if it is supplied', () => { - const container = document.createElement('div'); - const node = render(, container); - expect(node.name).toBe('name'); - expect(container.firstChild.getAttribute('name')).toBe('name'); - }); - - it('should render name attribute if it is supplied for SSR', () => { - const element = ; - const markup = ReactDOMServer.renderToString(element); - const div = document.createElement('div'); - div.innerHTML = markup; - expect(div.firstChild.getAttribute('name')).toBe('name'); - }); - - it('should not render name attribute if it is not supplied', () => { - const container = document.createElement('div'); - render(, container); - expect(container.firstChild.getAttribute('name')).toBe(null); - }); - - it('should not render name attribute if it is not supplied for SSR', () => { - const element = ; - const markup = ReactDOMServer.renderToString(element); - const div = document.createElement('div'); - div.innerHTML = markup; - expect(div.firstChild.getAttribute('name')).toBe(null); - }); - - it('should display "foobar" for `defaultValue` of `objToString`', () => { - const objToString = { - toString: function() { - return 'foobar'; - }, - }; - - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - expect(node.value).toBe('foobar'); - }); - - it('should display `value` of number 0', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - expect(node.value).toBe('0'); - }); - - it('should allow setting `value` to `true`', () => { - const container = document.createElement('div'); - let stub = ; - const node = render(stub, container); - - expect(node.value).toBe('yolo'); - - stub = render( - , - container, - ); - expect(node.value).toEqual('true'); - }); - - it('should allow setting `value` to `false`', () => { - const container = document.createElement('div'); - let stub = ; - const node = render(stub, container); - - expect(node.value).toBe('yolo'); - - stub = render( - , - container, - ); - expect(node.value).toEqual('false'); - }); - - it('should allow setting `value` to `objToString`', () => { - const container = document.createElement('div'); - let stub = ; - const node = render(stub, container); - - expect(node.value).toBe('foo'); - - const objToString = { - toString: function() { - return 'foobar'; - }, - }; - stub = render( - , - container, - ); - expect(node.value).toEqual('foobar'); - }); - - it('should not incur unnecessary DOM mutations', () => { - const container = document.createElement('div'); - render( {}} />, container); - - const node = container.firstChild; - let nodeValue = 'a'; - const nodeValueSetter = jest.genMockFn(); - Object.defineProperty(node, 'value', { - get: function() { - return nodeValue; - }, - set: nodeValueSetter.mockImplementation(function(newValue) { - nodeValue = newValue; - }), - }); - - render( {}} />, container); - expect(nodeValueSetter.mock.calls.length).toBe(0); - - render( {}} />, container); - expect(nodeValueSetter.mock.calls.length).toBe(1); - }); - - it('should not incur unnecessary DOM mutations for numeric type conversion', () => { - const container = document.createElement('div'); - render( {}} />, container); - - const node = container.firstChild; - let nodeValue = '0'; - const nodeValueSetter = jest.genMockFn(); - Object.defineProperty(node, 'value', { - get: function() { - return nodeValue; - }, - set: nodeValueSetter.mockImplementation(function(newValue) { - nodeValue = newValue; - }), - }); - - render( {}} />, container); - expect(nodeValueSetter.mock.calls.length).toBe(0); - }); - - it('should not incur unnecessary DOM mutations for the boolean type conversion', () => { - const container = document.createElement('div'); - render( {}} />, container); - - const node = container.firstChild; - let nodeValue = 'true'; - const nodeValueSetter = jest.genMockFn(); - Object.defineProperty(node, 'value', { - get: function() { - return nodeValue; - }, - set: nodeValueSetter.mockImplementation(function(newValue) { - nodeValue = newValue; - }), - }); - - render( {}} />, container); - expect(nodeValueSetter.mock.calls.length).toBe(0); - }); - - it('should properly control a value of number `0`', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - node.value = 'giraffe'; - ReactTestUtils.Simulate.change(node); - expect(node.value).toBe('0'); - }); - - it('should properly control 0.0 for a text input', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - node.value = '0.0'; - ReactTestUtils.Simulate.change(node, { target: { value: '0.0' } }); - expect(node.value).toBe('0'); - }); - - it('should properly control 0.0 for a number input', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - node.value = '0.0'; - ReactTestUtils.Simulate.change(node, { target: { value: '0.0' } }); - expect(node.value).toBe('0.0'); - }); - - it('should properly transition from an empty value to 0', function() { - const container = document.createElement('div'); - - render(, container); - render(, container); - - const node = container.firstChild; - - expect(node.value).toBe('0'); - expect(node.defaultValue).toBe('0'); - }); - - it('should properly transition from 0 to an empty value', function() { - const container = document.createElement('div'); - - render(, container); - render(, container); - - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.defaultValue).toBe(''); - }); - - it('should properly transition a text input from 0 to an empty 0.0', function() { - const container = document.createElement('div'); - - render(, container); - render(, container); - - const node = container.firstChild; - - expect(node.value).toBe('0.0'); - expect(node.defaultValue).toBe('0.0'); - }); - - it('should properly transition a number input from "" to 0', function() { - const container = document.createElement('div'); - - render(, container); - render(, container); - - const node = container.firstChild; - - expect(node.value).toBe('0'); - expect(node.defaultValue).toBe('0'); - }); - - it('should properly transition a number input from "" to "0"', function() { - const container = document.createElement('div'); - - render(, container); - render(, container); - - const node = container.firstChild; - - expect(node.value).toBe('0'); - expect(node.defaultValue).toBe('0'); - }); - - it('should have the correct target value', () => { - let handled = false; - const handler = function(event) { - expect(event.target.nodeName).toBe('INPUT'); - handled = true; - }; - const stub = ; - const container = document.createElement('div'); - const node = render(stub, container); - - setUntrackedValue.call(node, 'giraffe'); - - const fakeNativeEvent = function() {}; - fakeNativeEvent.target = node; - fakeNativeEvent.path = [node, container]; - ReactTestUtils.simulateNativeEventOnNode('topInput', node, fakeNativeEvent); - - expect(handled).toBe(true); - }); - - it('should not set a value for submit buttons unnecessarily', () => { - let stub = ; - stub = ReactTestUtils.renderIntoDocument(stub); - const node = findDOMNode(stub); - - // The value shouldn't be '', or else the button will have no text; it - // should have the default "Submit" or "Submit Query" label. Most browsers - // report this as not having a `value` attribute at all; IE reports it as - // the actual label that the user sees. - expect( - !node.hasAttribute('value') || node.getAttribute('value').length > 0, - ).toBe(true); - }); - - it('should control radio buttons', () => { - class RadioGroup extends React.Component { - render() { - return ( -
- - A - - B -
- -
-
- ); - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const aNode = stub.refs.a; - const bNode = stub.refs.b; - const cNode = stub.refs.c; - - expect(aNode.checked).toBe(true); - expect(bNode.checked).toBe(false); - // c is in a separate form and shouldn't be affected at all here - expect(cNode.checked).toBe(true); - - bNode.checked = true; - // This next line isn't necessary in a proper browser environment, but - // jsdom doesn't uncheck the others in a group (which makes this whole test - // a little less effective) - aNode.checked = false; - expect(cNode.checked).toBe(true); - - // Now let's run the actual ReactDOMInput change event handler - ReactTestUtils.Simulate.change(bNode); - - // The original state should have been restored - expect(aNode.checked).toBe(true); - expect(cNode.checked).toBe(true); - }); - - it('should check the correct radio when the selected name moves', () => { - class App extends React.Component { - state = { - updated: false, - }; - onClick = () => { - this.setState({ updated: true }); - }; - render() { - const { updated } = this.state; - const radioName = updated ? 'secondName' : 'firstName'; - return ( -
-
- ); - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - const buttonNode = findDOMNode(stub).childNodes[0]; - const firstRadioNode = findDOMNode(stub).childNodes[1]; - expect(firstRadioNode.checked).toBe(false); - ReactTestUtils.Simulate.click(buttonNode); - expect(firstRadioNode.checked).toBe(true); - }); - - it('should control radio buttons if the tree updates during render', () => { - const sharedParent = document.createElement('div'); - const container1 = document.createElement('div'); - const container2 = document.createElement('div'); - - sharedParent.appendChild(container1); - - let aNode; - let bNode; - class ComponentA extends React.Component { - componentDidMount() { - render(, container2); - } - render() { - return ( -
- (aNode = n)} - type="radio" - name="fruit" - checked={true} - onChange={emptyFunction} - /> - A -
- ); - } - } - - class ComponentB extends React.Component { - state = { changed: false }; - handleChange = () => { - this.setState({ - changed: true, - }); - }; - componentDidUpdate() { - sharedParent.appendChild(container2); - } - render() { - return ( -
- (bNode = n)} - type="radio" - name="fruit" - checked={false} - onChange={this.handleChange} - /> - B -
- ); - } - } - - render(, container1); - - expect(aNode.checked).toBe(true); - expect(bNode.checked).toBe(false); - - bNode.checked = true; - // This next line isn't necessary in a proper browser environment, but - // jsdom doesn't uncheck the others in a group (which makes this whole test - // a little less effective) - aNode.checked = false; - - // Now let's run the actual ReactDOMInput change event handler - ReactTestUtils.Simulate.change(bNode); - - // The original state should have been restored - expect(aNode.checked).toBe(true); - expect(bNode.checked).toBe(false); - }); - - it('should warn with value and no onChange handler and readOnly specified', () => { - spyOnDev(console, 'error'); - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(0); - } - - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Failed prop type: You provided a `value` prop to a form ' + - 'field without an `onChange` handler. This will render a read-only ' + - 'field. If the field should be mutable use `defaultValue`. ' + - 'Otherwise, set either `onChange` or `readOnly`.\n' + - ' in input (at **)', - ); - } - }); - - it('should have a this value of undefined if bind is not used', () => { - const unboundInputOnChange = function() { - expect(this).toBe(undefined); - }; - - let instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - - ReactTestUtils.Simulate.change(instance); - }); - - it('should warn with checked and no onChange handler with readOnly specified', () => { - spyOnDev(console, 'error'); - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(0); - } - - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - } - }); - - it('should update defaultValue to empty string', () => { - const container = document.createElement('div'); - render(, container); - render(, container); - expect(container.firstChild.defaultValue).toBe(''); - }); - - it('should warn if value is null', () => { - spyOnDev(console, 'error'); - ReactTestUtils.renderIntoDocument(); - if (__DEV__) { - expect(console.error.calls.argsFor(0)[0]).toContain( - '`value` prop on `input` should not be null. ' + - 'Consider using an empty string to clear the component or `undefined` ' + - 'for uncontrolled components.', - ); - } - - ReactTestUtils.renderIntoDocument(); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - } - }); - - it('should warn if checked and defaultChecked props are specified', () => { - spyOnDev(console, 'error'); - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.argsFor(0)[0]).toContain( - 'A component contains an input of type radio with both checked and defaultChecked props. ' + - 'Input elements must be either controlled or uncontrolled ' + - '(specify either the checked prop, or the defaultChecked prop, but not ' + - 'both). Decide between using a controlled or uncontrolled input ' + - 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components', - ); - } - - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - } - }); - - it('should warn if value and defaultValue props are specified', () => { - spyOnDev(console, 'error'); - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.argsFor(0)[0]).toContain( - 'A component contains an input of type text with both value and defaultValue props. ' + - 'Input elements must be either controlled or uncontrolled ' + - '(specify either the value prop, or the defaultValue prop, but not ' + - 'both). Decide between using a controlled or uncontrolled input ' + - 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components', - ); - } - - ReactTestUtils.renderIntoDocument( - , - ); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - } - }); - - it('should warn if controlled input switches to uncontrolled (value is undefined)', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled input switches to uncontrolled (value is null)', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBeGreaterThan(0); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled input switches to uncontrolled with defaultValue', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled input (value is undefined) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled input (value is null) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBeGreaterThan(0); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => { - spyOnDev(console, 'error'); - const stub = ( - - ); - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled radio switches to uncontrolled (checked is null)', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should warn if uncontrolled radio (checked is null) switches to controlled', () => { - spyOnDev(console, 'error'); - const stub = ; - const container = document.createElement('div'); - render(stub, container); - render(, container); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('should not warn if radio value changes but never becomes controlled', () => { - const container = document.createElement('div'); - render(, container); - render(, container); - render( - , - container, - ); - render( - null} />, - container, - ); - render(, container); - }); - - it('should not warn if radio value changes but never becomes uncontrolled', () => { - const container = document.createElement('div'); - render( - null} />, - container, - ); - render( - null} - />, - container, - ); - }); - - it('should warn if radio checked false changes to become uncontrolled', () => { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - render( - null} - />, - container, - ); - render(, container); - if (__DEV__) { - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); - } - }); - - it('sets type, step, min, max before value always', () => { - const log = []; - const originalCreateElement = document.createElement; - spyOnDevAndProd(document, 'createElement').and.callFake(function(type) { - const el = originalCreateElement.apply(this, arguments); - let value = ''; - - if (type === 'input') { - Object.defineProperty(el, 'value', { - get: function() { - return value; - }, - set: function(val) { - value = '' + val; - log.push('set property value'); - }, - }); - spyOnDevAndProd(el, 'setAttribute').and.callFake(function(name) { - log.push('set attribute ' + name); - }); - } - return el; - }); - - ReactTestUtils.renderIntoDocument( - {}} - type="range" - min="0" - max="100" - step="1" - />, - ); - expect(log).toEqual([ - 'set attribute type', - 'set attribute min', - 'set attribute max', - 'set attribute step', - 'set property value', - 'set attribute value', - 'set attribute checked', - 'set attribute checked', - ]); - }); - - it('sets value properly with type coming later in props', () => { - const input = ReactTestUtils.renderIntoDocument( - , - ); - expect(input.value).toBe('hi'); - }); - - it('does not raise a validation warning when it switches types', () => { - class Input extends React.Component { - state = { type: 'number', value: 1000 }; - - render() { - const { value, type } = this.state; - return {}} type={type} value={value} />; - } - } - - const input = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(input); - - // If the value is set before the type, a validation warning will raise and - // the value will not be assigned. - input.setState({ type: 'text', value: 'Test' }); - expect(node.value).toEqual('Test'); - }); - - it('resets value of date/time input to fix bugs in iOS Safari', () => { - function strify(x) { - return JSON.stringify(x, null, 2); - } - - const log = []; - const originalCreateElement = document.createElement; - spyOnDevAndProd(document, 'createElement').and.callFake(function(type) { - const el = originalCreateElement.apply(this, arguments); - let value = ''; - if (type === 'input') { - Object.defineProperty(el, 'value', { - get: function() { - return value; - }, - set: function(val) { - value = '' + val; - log.push(`node.value = ${strify(val)}`); - }, - }); - spyOnDevAndProd(el, 'setAttribute').and.callFake(function(name, val) { - log.push(`node.setAttribute(${strify(name)}, ${strify(val)})`); - }); - } - return el; - }); - - ReactTestUtils.renderIntoDocument( - , - ); - expect(log).toEqual([ - 'node.setAttribute("type", "date")', - 'node.value = "1980-01-01"', - 'node.setAttribute("value", "1980-01-01")', - 'node.setAttribute("checked", "")', - 'node.setAttribute("checked", "")', - ]); - }); - - describe('assigning the value attribute on controlled inputs', function() { - function getTestInput() { - return class extends React.Component { - state = { - value: this.props.value == null ? '' : this.props.value, - }; - onChange = event => { - this.setState({ value: event.target.value }); - }; - render() { - const type = this.props.type; - const value = this.state.value; - - return ; - } - }; - } - - it('always sets the attribute when values change on text inputs', function() { - const Input = getTestInput(); - const stub = ReactTestUtils.renderIntoDocument(); - const node = findDOMNode(stub); - - ReactTestUtils.Simulate.change(node, { target: { value: '2' } }); - - expect(node.getAttribute('value')).toBe('2'); - }); - - it('does not set the value attribute on number inputs if focused', () => { - const Input = getTestInput(); - const stub = ReactTestUtils.renderIntoDocument( - , - ); - const node = findDOMNode(stub); - - node.focus(); - - ReactTestUtils.Simulate.change(node, { target: { value: '2' } }); - - expect(node.getAttribute('value')).toBe('1'); - }); - - it('sets the value attribute on number inputs on blur', () => { - const Input = getTestInput(); - const stub = ReactTestUtils.renderIntoDocument( - , - ); - const node = findDOMNode(stub); - - ReactTestUtils.Simulate.change(node, { target: { value: '2' } }); - ReactTestUtils.SimulateNative.blur(node); - - expect(node.getAttribute('value')).toBe('2'); - }); - - it('an uncontrolled number input will not update the value attribute on blur', () => { - const stub = ReactTestUtils.renderIntoDocument( - , - ); - const node = findDOMNode(stub); - - node.value = 4; - - ReactTestUtils.SimulateNative.blur(node); - - expect(node.getAttribute('value')).toBe('1'); - }); - - it('an uncontrolled text input will not update the value attribute on blur', () => { - const stub = ReactTestUtils.renderIntoDocument( - , - ); - const node = findDOMNode(stub); - - node.value = 4; - - ReactTestUtils.SimulateNative.blur(node); - - expect(node.getAttribute('value')).toBe('1'); - }); - }); - - describe('setting a controlled input to undefined', () => { - let input; - - function renderInputWithStringThenWithUndefined() { - class Input extends React.Component { - state = { value: 'first' }; - render() { - return ( - this.setState({ value: e.target.value })} - value={this.state.value} - /> - ); - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - input = findDOMNode(stub); - ReactTestUtils.Simulate.change(input, { target: { value: 'latest' } }); - ReactTestUtils.Simulate.change(input, { target: { value: undefined } }); - } - - it('reverts the value attribute to the initial value', () => { - spyOnDev(console, 'error'); - renderInputWithStringThenWithUndefined(); - expect(input.getAttribute('value')).toBe('first'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Input elements should not switch from controlled to ' + - 'uncontrolled (or vice versa).', - ); - } - }); - - it('preserves the value property', () => { - spyOnDev(console, 'error'); - renderInputWithStringThenWithUndefined(); - expect(input.value).toBe('latest'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Input elements should not switch from controlled to ' + - 'uncontrolled (or vice versa).', - ); - } - }); - }); - - describe('setting a controlled input to null', () => { - let input; - - function renderInputWithStringThenWithNull() { - class Input extends React.Component { - state = { value: 'first' }; - render() { - return ( - this.setState({ value: e.target.value })} - value={this.state.value} - /> - ); - } - } - - const stub = ReactTestUtils.renderIntoDocument(); - input = findDOMNode(stub); - ReactTestUtils.Simulate.change(input, { target: { value: 'latest' } }); - ReactTestUtils.Simulate.change(input, { target: { value: null } }); - } - - it('reverts the value attribute to the initial value', () => { - spyOnDev(console, 'error'); - renderInputWithStringThenWithNull(); - expect(input.getAttribute('value')).toBe('first'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(2); - expect(console.error.calls.argsFor(0)[0]).toContain( - '`value` prop on `input` should not be null. ' + - 'Consider using an empty string to clear the component ' + - 'or `undefined` for uncontrolled components.', - ); - expect(console.error.calls.argsFor(1)[0]).toContain( - 'Input elements should not switch from controlled ' + - 'to uncontrolled (or vice versa).', - ); - } - }); - - it('preserves the value property', () => { - spyOnDev(console, 'error'); - renderInputWithStringThenWithNull(); - expect(input.value).toBe('latest'); - if (__DEV__) { - expect(console.error.calls.count()).toBe(2); - expect(console.error.calls.argsFor(0)[0]).toContain( - '`value` prop on `input` should not be null. ' + - 'Consider using an empty string to clear the component ' + - 'or `undefined` for uncontrolled components.', - ); - expect(console.error.calls.argsFor(1)[0]).toContain( - 'Input elements should not switch from controlled ' + - 'to uncontrolled (or vice versa).', - ); - } - }); - }); - - describe('When given a Symbol value', function() { - it('treats initial Symbol value as an empty string', function() { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - render( {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Invalid value for prop `value`', - ); - } - }); - - it('treats updated Symbol value as an empty string', function() { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - render( {}} />, container); - render( {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Invalid value for prop `value`', - ); - } - }); - - it('treats initial Symbol defaultValue as an empty string', function() { - const container = document.createElement('div'); - render(, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - // TODO: we should warn here. - }); - - it('treats updated Symbol defaultValue as an empty string', function() { - const container = document.createElement('div'); - render(, container); - render(, container); - const node = container.firstChild; - - expect(node.value).toBe('foo'); - expect(node.getAttribute('value')).toBe(''); - // TODO: we should warn here. - }); - }); - - describe('When given a function value', function() { - it('treats initial function value as an empty string', function() { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - render( {}} onChange={() => {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Invalid value for prop `value`', - ); - } - }); - - it('treats updated function value as an empty string', function() { - spyOnDev(console, 'error'); - const container = document.createElement('div'); - render( {}} />, container); - render( {}} onChange={() => {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - - if (__DEV__) { - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]) - .toContain - // 'Invalid value for prop `value`' - (); - } - }); - - it('treats initial function defaultValue as an empty string', function() { - const container = document.createElement('div'); - render( {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe(''); - expect(node.getAttribute('value')).toBe(''); - // TODO: we should warn here. - }); - - it('treats updated function defaultValue as an empty string', function() { - const container = document.createElement('div'); - render(, container); - render( {}} />, container); - const node = container.firstChild; - - expect(node.value).toBe('foo'); - expect(node.getAttribute('value')).toBe(''); - // TODO: we should warn here. - }); - }); -});