diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js deleted file mode 100644 index 5f717d8be12ee..0000000000000 --- a/fixtures/dom/src/components/Header.js +++ /dev/null @@ -1,70 +0,0 @@ -import { parse, stringify } from 'query-string'; -import getVersionTags from '../tags'; -const React = window.React; - -const Header = React.createClass({ - getInitialState() { - const query = parse(window.location.search); - const version = query.version || 'local'; - const versions = [version]; - return { version, versions }; - }, - componentWillMount() { - getVersionTags() - .then(tags => { - let versions = tags.map(tag => tag.name.slice(1)); - versions = [`local`, ...versions]; - this.setState({ versions }); - }) - }, - handleVersionChange(event) { - const query = parse(window.location.search); - query.version = event.target.value; - if (query.version === 'local') { - delete query.version; - } - window.location.search = stringify(query); - }, - handleFixtureChange(event) { - window.location.pathname = event.target.value; - }, - render() { - return ( -
-
- - - React Sandbox (v{React.version}) - - -
- - -
-
-
- ); - }, -}); - -export default Header; diff --git a/fixtures/dom/src/components/fixtures/index.js b/fixtures/dom/src/components/fixtures/index.js deleted file mode 100644 index 516e8ad3aee27..0000000000000 --- a/fixtures/dom/src/components/fixtures/index.js +++ /dev/null @@ -1,34 +0,0 @@ -const React = window.React; -import RangeInputFixtures from './range-inputs'; -import TextInputFixtures from './text-inputs'; -import SelectFixtures from './selects'; -import TextAreaFixtures from './textareas'; -import InputChangeEvents from './input-change-events'; -import NumberInputFixtures from './number-inputs/'; - -/** - * A simple routing component that renders the appropriate - * fixture based on the location pathname. - */ -const FixturesPage = React.createClass({ - render() { - switch (window.location.pathname) { - case '/text-inputs': - return ; - case '/range-inputs': - return ; - case '/selects': - return ; - case '/textareas': - return ; - case '/input-change-events': - return ; - case '/number-inputs': - return ; - default: - return

Please select a test fixture.

; - } - }, -}); - -module.exports = FixturesPage; diff --git a/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js b/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js deleted file mode 100644 index 2c072d478ebe4..0000000000000 --- a/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js +++ /dev/null @@ -1,37 +0,0 @@ -const React = window.React; - -import Fixture from '../../Fixture'; - -const NumberTestCase = React.createClass({ - getInitialState() { - return { value: '' }; - }, - onChange(event) { - const parsed = parseFloat(event.target.value, 10) - const value = isNaN(parsed) ? '' : parsed - - this.setState({ value }) - }, - render() { - return ( - -
{this.props.children}
- -
-
- Controlled - - Value: {JSON.stringify(this.state.value)} -
- -
- Uncontrolled - -
-
-
- ); - }, -}); - -export default NumberTestCase; diff --git a/fixtures/dom/src/components/fixtures/number-inputs/index.js b/fixtures/dom/src/components/fixtures/number-inputs/index.js deleted file mode 100644 index 2c88c333edeb3..0000000000000 --- a/fixtures/dom/src/components/fixtures/number-inputs/index.js +++ /dev/null @@ -1,167 +0,0 @@ -const React = window.React; - -import FixtureSet from '../../FixtureSet'; -import TestCase from '../../TestCase'; -import NumberTestCase from './NumberTestCase'; - -const NumberInputs = React.createClass({ - render() { - return ( - - - -
  • Type "3.1"
  • -
  • Press backspace, eliminating the "1"
  • -
    - - - The field should read "3.", preserving the decimal place - - - - -

    - Notes: Chrome and Safari clear trailing - decimals on blur. React makes this concession so that the - value attribute remains in sync with the value property. -

    -
    - - - -
  • Type "0.01"
  • -
    - - - The field should read "0.01" - - - -
    - - - -
  • Type "2e"
  • -
  • Type 4, to read "2e4"
  • -
    - - - The field should read "2e4". The parsed value should read "20000" - - - -
    - - - -
  • Type "3.14"
  • -
  • Press "e", so that the input reads "3.14e"
  • -
    - - - The field should read "3.14e", the parsed value should be empty - - - -
    - - - -
  • Type "3.14"
  • -
  • Move the text cursor to after the decimal place
  • -
  • Press "e" twice, so that the value reads "3.ee14"
  • -
    - - - The field should read "3.ee14" - - - -
    - - - -
  • Type "3.0"
  • -
    - - - The field should read "3.0" - - - -
    - - - -
  • Type "300"
  • -
  • Move the cursor to after the "3"
  • -
  • Type "."
  • -
    - - - The field should read "3.00", not "3" - - -
    - - - -
  • Type "3"
  • -
  • Select the entire value"
  • -
  • Type '-' to replace '3' with '-'
  • -
    - - - The field should read "-", not be blank. - - -
    - - - -
  • Type "-"
  • -
  • Type '3'
  • -
    - - - The field should read "-3". - - -
    -
    - ); - }, -}); - -export default NumberInputs; diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index 6cdcde91201da..7263bca413f34 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -226,8 +226,7 @@ var ReactDOMInput = { // browsers typically do this as necessary, jsdom doesn't. node.value = '' + value; } - // eslint-disable-next-line - } else if (value != node.value) { + } else if (node.value !== value) { // Cast `value` to a string to ensure the value is set correctly. While // browsers typically do this as necessary, jsdom doesn't. node.value = '' + value; diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index 2240b37502ad0..554bf0a309204 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -32,6 +32,196 @@ describe('ReactDOMInput', () => { spyOn(console, 'error'); }); + it('should properly control a value even if no event listener exists', () => { + var container = document.createElement('div'); + var stub = ReactDOM.render(, container); + + document.body.appendChild(container); + + var node = ReactDOM.findDOMNode(stub); + expectDev(console.error.calls.count()).toBe(1); + + // Simulate a native change event + setUntrackedValue(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. + var nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('change', true, true); + node.dispatchEvent(nativeEvent); + + expect(node.value).toBe('lion'); + + document.body.removeChild(container); + }); + + it('should control a value in reentrant events', () => { + // 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. + var inputEvent = document.createEvent('Event'); + inputEvent.initEvent('input', true, true); + // 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. + var changeEvent = document.createEvent('Event'); + changeEvent.initEvent('change', true, true); + + 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. + // I don't know how to simulate a change event on a text box. + this.a.dispatchEvent(changeEvent); + 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)} /> +
    + ); + } + } + + var container = document.createElement('div'); + var instance = ReactDOM.render(, container); + + // We need it to be in the body to test native event dispatching. + document.body.appendChild(container); + + instance.a.focus(); + // Simulate a native keyup event + setUntrackedValue(instance.a, 'giraffe'); + + instance.a.dispatchEvent(inputEvent); + + 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', () => { + // 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. + var inputEvent = document.createEvent('Event'); + inputEvent.initEvent('input', true, true); + + 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} /> +
    + ); + } + } + + var container = document.createElement('div'); + var instance = ReactDOM.render(, container); + + // We need it to be in the body to test native event dispatching. + document.body.appendChild(container); + + // Simulate a native keyup event + setUntrackedValue(instance.a, 'giraffe'); + instance.a.dispatchEvent(inputEvent); + + // These should now both have been restored to their controlled value. + + 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', () => { + var stub = ; + stub = ReactTestUtils.renderIntoDocument(stub); + var node = ReactDOM.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', () => { + var stub = ; + stub = ReactTestUtils.renderIntoDocument(stub); + var node = ReactDOM.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 ; + } + } + + var stub = ReactTestUtils.renderIntoDocument(); + var node = ReactDOM.findDOMNode(stub); + + node.value = '2.0'; + + ReactTestUtils.Simulate.change(node); + + expect(node.getAttribute('value')).toBe('2.0'); + expect(node.value).toBe('2.0'); + }); + }); + it('should display `defaultValue` of number 0', () => { var stub = ; stub = ReactTestUtils.renderIntoDocument(stub); @@ -287,7 +477,7 @@ describe('ReactDOMInput', () => { node.value = '0.0'; ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}}); - expect(node.value).toBe('0.0'); + expect(node.value).toBe('0'); }); it('should properly control 0.0 for a number input', () => {