Use only public API for ChangeEventPlugin-test.js (#11333)

* Use only public API for ChangeEventPlugin-test.js

* precommit commands complete

* Removed comments

* Improving event dispatchers

* Updated tests

* Fixed for revisions

* Prettified

* Add more details and fixes to tests

* Not internal anymore

* Remove unused code
This commit is contained in:
Ethan Arrowood
2017-11-24 22:18:40 +00:00
committed by Dan Abramov
parent 7d27851bf4
commit a67757e115
2 changed files with 260 additions and 222 deletions
@@ -1,222 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var ReactTestUtils = require('react-dom/test-utils');
// TODO: can we express this test with only public API?
var ChangeEventPlugin = require('../ChangeEventPlugin').default;
var inputValueTracking = require('../../client/inputValueTracking');
function getTrackedValue(elem) {
var tracker = inputValueTracking._getTrackerFromNode(elem);
return tracker.getValue();
}
function setTrackedValue(elem, value) {
var tracker = inputValueTracking._getTrackerFromNode(elem);
tracker.setValue(value);
}
function setUntrackedValue(elem, value) {
var tracker = inputValueTracking._getTrackerFromNode(elem);
var current = tracker.getValue();
if (elem.type === 'checkbox' || elem.type === 'radio') {
elem.checked = value;
} else {
elem.value = value;
}
tracker.setValue(current);
}
describe('ChangeEventPlugin', () => {
it('should fire change for checkbox input', () => {
var called = 0;
function cb(e) {
called = 1;
expect(e.type).toBe('change');
}
var input = ReactTestUtils.renderIntoDocument(
<input type="checkbox" onChange={cb} />,
);
setUntrackedValue(input, true);
ReactTestUtils.SimulateNative.click(input);
expect(called).toBe(1);
});
it('should catch setting the value programmatically', () => {
var input = ReactTestUtils.renderIntoDocument(
<input type="text" defaultValue="foo" />,
);
input.value = 'bar';
expect(getTrackedValue(input)).toBe('bar');
});
it('should not fire change when setting the value programmatically', () => {
var called = 0;
function cb(e) {
called += 1;
expect(e.type).toBe('change');
}
var input = ReactTestUtils.renderIntoDocument(
<input type="text" onChange={cb} defaultValue="foo" />,
);
input.value = 'bar';
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(0);
setUntrackedValue(input, 'foo');
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(1);
});
it('should not fire change when setting checked programmatically', () => {
var called = 0;
function cb(e) {
called += 1;
expect(e.type).toBe('change');
}
var input = ReactTestUtils.renderIntoDocument(
<input type="checkbox" onChange={cb} defaultChecked={true} />,
);
input.checked = true;
ReactTestUtils.SimulateNative.click(input);
expect(called).toBe(0);
input.checked = false;
setTrackedValue(input, undefined);
ReactTestUtils.SimulateNative.click(input);
expect(called).toBe(1);
});
it('should unmount', () => {
var container = document.createElement('div');
var input = ReactDOM.render(<input />, container);
ReactDOM.unmountComponentAtNode(container);
});
it('should only fire change for checked radio button once', () => {
var called = 0;
function cb(e) {
called += 1;
}
var input = ReactTestUtils.renderIntoDocument(
<input type="radio" onChange={cb} />,
);
setUntrackedValue(input, true);
ReactTestUtils.SimulateNative.click(input);
ReactTestUtils.SimulateNative.click(input);
expect(called).toBe(1);
});
it('should deduplicate input value change events', () => {
var input;
var called = 0;
function cb(e) {
called += 1;
expect(e.type).toBe('change');
}
[
<input type="text" onChange={cb} />,
<input type="number" onChange={cb} />,
<input type="range" onChange={cb} />,
].forEach(function(element) {
called = 0;
input = ReactTestUtils.renderIntoDocument(element);
setUntrackedValue(input, '40');
ReactTestUtils.SimulateNative.change(input);
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(1);
called = 0;
input = ReactTestUtils.renderIntoDocument(element);
setUntrackedValue(input, '40');
ReactTestUtils.SimulateNative.input(input);
ReactTestUtils.SimulateNative.input(input);
expect(called).toBe(1);
called = 0;
input = ReactTestUtils.renderIntoDocument(element);
setUntrackedValue(input, '40');
ReactTestUtils.SimulateNative.input(input);
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(1);
});
});
it('should listen for both change and input events when supported', () => {
var called = 0;
function cb(e) {
called += 1;
expect(e.type).toBe('change');
}
if (!ChangeEventPlugin._isInputEventSupported) {
return;
}
var input = ReactTestUtils.renderIntoDocument(
<input type="range" onChange={cb} />,
);
setUntrackedValue(input, 'bar');
ReactTestUtils.SimulateNative.input(input);
setUntrackedValue(input, 'foo');
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(2);
});
it('should only fire events when the value changes for range inputs', () => {
var called = 0;
function cb(e) {
called += 1;
expect(e.type).toBe('change');
}
var input = ReactTestUtils.renderIntoDocument(
<input type="range" onChange={cb} />,
);
setUntrackedValue(input, '40');
ReactTestUtils.SimulateNative.input(input);
ReactTestUtils.SimulateNative.change(input);
setUntrackedValue(input, 'foo');
ReactTestUtils.SimulateNative.input(input);
ReactTestUtils.SimulateNative.change(input);
expect(called).toBe(2);
});
});
@@ -0,0 +1,260 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var setUntrackedChecked = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'checked',
).set;
var setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'value',
).set;
describe('ChangeEventPlugin', () => {
var container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('should fire change for checkbox input', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var node = ReactDOM.render(
<input type="checkbox" onChange={cb} />,
container,
);
expect(node.checked).toBe(false);
node.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
// Note: unlike with text input events, dispatching `click` actually
// toggles the checkbox and updates its `checked` value.
expect(node.checked).toBe(true);
expect(called).toBe(1);
expect(node.checked).toBe(true);
node.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
expect(node.checked).toBe(false);
expect(called).toBe(2);
});
it('should not fire change setting the value programmatically', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input = ReactDOM.render(
<input type="text" defaultValue="foo" onChange={cb} />,
container,
);
// We try to avoid firing "duplicate" React change events.
// However, to tell which events are duplicates and should be ignored,
// we are tracking the "current" input value, and only respect events
// that occur after it changes. In this test, we verify that we can
// keep track of the "current" value even if it is set programatically.
// Set it programmatically.
input.value = 'bar';
// Even if a DOM input event fires, React sees that the real input value now
// ('bar') is the same as the "current" one we already recorded.
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
expect(input.value).toBe('bar');
// In this case we don't expect to get a React event.
expect(called).toBe(0);
// However, we can simulate user typing by calling the underlying setter.
setUntrackedValue.call(input, 'foo');
// Now, when the event fires, the real input value ('foo') differs from the
// "current" one we previously recorded ('bar').
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
expect(input.value).toBe('foo');
// In this case React should fire an event for it.
expect(called).toBe(1);
// Verify again that extra events without real changes are ignored.
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
expect(called).toBe(1);
});
// See a similar input test above for a detailed description of why.
it('should not fire change when setting checked programmatically', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input = ReactDOM.render(
<input type="checkbox" onChange={cb} defaultChecked={false} />,
container,
);
// Set the value, updating the "current" value that React tracks to true.
input.checked = true;
// Under the hood, uncheck the box so that the click will "check" it again.
setUntrackedChecked.call(input, false);
input.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
expect(input.checked).toBe(true);
// We don't expect a React event because at the time of the click, the real
// checked value (true) was the same as the last recorded "current" value
// (also true).
expect(called).toBe(0);
// However, simulating a normal click should fire a React event because the
// real value (false) would have changed from the last tracked value (true).
input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
expect(called).toBe(1);
});
it('should unmount', () => {
var input = ReactDOM.render(<input />, container);
ReactDOM.unmountComponentAtNode(container);
});
it('should only fire change for checked radio button once', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input = ReactDOM.render(
<input type="radio" onChange={cb} />,
container,
);
setUntrackedChecked.call(input, true);
input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
expect(called).toBe(1);
});
it('should deduplicate input value change events', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input;
['text', 'number', 'range'].forEach(type => {
called = 0;
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
setUntrackedValue.call(input, '42');
input.dispatchEvent(
new Event('change', {bubbles: true, cancelable: true}),
);
input.dispatchEvent(
new Event('change', {bubbles: true, cancelable: true}),
);
expect(called).toBe(1);
ReactDOM.unmountComponentAtNode(container);
called = 0;
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
setUntrackedValue.call(input, '42');
input.dispatchEvent(
new Event('input', {bubbles: true, cancelable: true}),
);
input.dispatchEvent(
new Event('input', {bubbles: true, cancelable: true}),
);
expect(called).toBe(1);
ReactDOM.unmountComponentAtNode(container);
called = 0;
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
setUntrackedValue.call(input, '42');
input.dispatchEvent(
new Event('input', {bubbles: true, cancelable: true}),
);
input.dispatchEvent(
new Event('change', {bubbles: true, cancelable: true}),
);
expect(called).toBe(1);
ReactDOM.unmountComponentAtNode(container);
});
});
it('should listen for both change and input events when supported', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input = ReactDOM.render(
<input type="range" onChange={cb} />,
container,
);
setUntrackedValue.call(input, 'foo');
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
setUntrackedValue.call(input, 'bar');
input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
expect(called).toBe(2);
});
it('should only fire events when the value changes for range inputs', () => {
var called = 0;
function cb(e) {
called++;
expect(e.type).toBe('change');
}
var input = ReactDOM.render(
<input type="range" onChange={cb} />,
container,
);
setUntrackedValue.call(input, '40');
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
setUntrackedValue.call(input, 'foo');
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
expect(called).toBe(2);
});
});