mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
committed by
Dan Abramov
parent
7d27851bf4
commit
a67757e115
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user