mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Replace inputValueTracking-test with public API tests (#11654)
This commit is contained in:
@@ -1,181 +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 inputValueTracking = require('../client/inputValueTracking');
|
||||
|
||||
var getTracker = inputValueTracking._getTrackerFromNode;
|
||||
|
||||
describe('inputValueTracking', () => {
|
||||
var input;
|
||||
|
||||
beforeEach(() => {
|
||||
input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
});
|
||||
|
||||
it('should attach tracker to node', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(<input type="text" />);
|
||||
|
||||
expect(getTracker(node)).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define `value` on the node instance', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(<input type="text" />);
|
||||
|
||||
expect(node.hasOwnProperty('value')).toBe(true);
|
||||
});
|
||||
|
||||
it('should define `checked` on the node instance', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(<input type="checkbox" />);
|
||||
|
||||
expect(node.hasOwnProperty('checked')).toBe(true);
|
||||
});
|
||||
|
||||
it('should initialize with the current value', () => {
|
||||
input.value = 'foo';
|
||||
|
||||
inputValueTracking.track(input);
|
||||
|
||||
var tracker = getTracker(input);
|
||||
|
||||
expect(tracker.getValue()).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should initialize with the current `checked`', () => {
|
||||
const checkbox = document.createElement('input');
|
||||
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = true;
|
||||
inputValueTracking.track(checkbox);
|
||||
|
||||
var tracker = getTracker(checkbox);
|
||||
|
||||
expect(tracker.getValue()).toEqual('true');
|
||||
});
|
||||
|
||||
it('should track value changes', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" defaultValue="foo" />,
|
||||
);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
|
||||
node.value = 'bar';
|
||||
expect(tracker.getValue()).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should tracked`checked` changes', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="checkbox" defaultChecked={true} />,
|
||||
);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
|
||||
node.checked = false;
|
||||
expect(tracker.getValue()).toEqual('false');
|
||||
});
|
||||
|
||||
it('should update value manually', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" defaultValue="foo" />,
|
||||
);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
|
||||
tracker.setValue('bar');
|
||||
expect(tracker.getValue()).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should coerce value to a string', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" defaultValue="foo" />,
|
||||
);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
|
||||
tracker.setValue(500);
|
||||
expect(tracker.getValue()).toEqual('500');
|
||||
});
|
||||
|
||||
it('should update value if it changed and return result', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" defaultValue="foo" />,
|
||||
);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
|
||||
expect(inputValueTracking.updateValueIfChanged(node)).toBe(false);
|
||||
|
||||
tracker.setValue('bar');
|
||||
|
||||
expect(inputValueTracking.updateValueIfChanged(node)).toBe(true);
|
||||
|
||||
expect(tracker.getValue()).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should return true when updating untracked instance', () => {
|
||||
input.value = 'foo';
|
||||
|
||||
expect(inputValueTracking.updateValueIfChanged(input)).toBe(true);
|
||||
|
||||
expect(getTracker(input)).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should return tracker from node', () => {
|
||||
var div = document.createElement('div');
|
||||
var node = ReactDOM.render(<input type="text" defaultValue="foo" />, div);
|
||||
|
||||
var tracker = getTracker(node);
|
||||
expect(tracker.getValue()).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should stop tracking', () => {
|
||||
inputValueTracking.track(input);
|
||||
|
||||
expect(getTracker(input)).not.toEqual(null);
|
||||
|
||||
inputValueTracking.stopTracking(input);
|
||||
|
||||
expect(getTracker(input)).toEqual(null);
|
||||
|
||||
expect(input.hasOwnProperty('value')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not crash for nodes with custom value property', () => {
|
||||
// https://github.com/facebook/react/issues/10196
|
||||
try {
|
||||
var originalCreateElement = document.createElement;
|
||||
document.createElement = function() {
|
||||
var node = originalCreateElement.apply(this, arguments);
|
||||
Object.defineProperty(node, 'value', {
|
||||
get() {},
|
||||
set() {},
|
||||
});
|
||||
return node;
|
||||
};
|
||||
var div = document.createElement('div');
|
||||
// Mount
|
||||
var node = ReactDOM.render(<input type="text" />, div);
|
||||
// Update
|
||||
ReactDOM.render(<input type="text" />, div);
|
||||
// Change
|
||||
ReactTestUtils.SimulateNative.change(node);
|
||||
// Unmount
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
} finally {
|
||||
document.createElement = originalCreateElement;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -96,11 +96,6 @@ function trackValueOnNode(node: any): ?ValueTracker {
|
||||
return tracker;
|
||||
}
|
||||
|
||||
export {
|
||||
// exposed for testing
|
||||
getTracker as _getTrackerFromNode,
|
||||
};
|
||||
|
||||
export function track(node: ElementWithValueTracker) {
|
||||
if (getTracker(node)) {
|
||||
return;
|
||||
|
||||
@@ -35,6 +35,79 @@ describe('ChangeEventPlugin', () => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
// 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 most of these tests, we verify that we
|
||||
// keep track of the "current" value and only fire events when it changes.
|
||||
// See https://github.com/facebook/react/pull/5746.
|
||||
|
||||
it('should consider initial text value to be current', () => {
|
||||
var called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
var node = ReactDOM.render(
|
||||
<input type="text" onChange={cb} defaultValue="foo" />,
|
||||
container,
|
||||
);
|
||||
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
|
||||
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
|
||||
// There should be no React change events because the value stayed the same.
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
it('should consider initial checkbox checked=true to be current', () => {
|
||||
var called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
var node = ReactDOM.render(
|
||||
<input type="checkbox" onChange={cb} defaultChecked={true} />,
|
||||
container,
|
||||
);
|
||||
|
||||
// Secretly, set `checked` to false, so that dispatching the `click` will
|
||||
// make it `true` again. Thus, at the time of the event, React should not
|
||||
// consider it a change from the initial `true` value.
|
||||
setUntrackedChecked.call(node, false);
|
||||
node.dispatchEvent(
|
||||
new MouseEvent('click', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// There should be no React change events because the value stayed the same.
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
it('should consider initial checkbox checked=false to be current', () => {
|
||||
var called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
var node = ReactDOM.render(
|
||||
<input type="checkbox" onChange={cb} defaultChecked={false} />,
|
||||
container,
|
||||
);
|
||||
|
||||
// Secretly, set `checked` to true, so that dispatching the `click` will
|
||||
// make it `false` again. Thus, at the time of the event, React should not
|
||||
// consider it a change from the initial `false` value.
|
||||
setUntrackedChecked.call(node, true);
|
||||
node.dispatchEvent(
|
||||
new MouseEvent('click', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// There should be no React change events because the value stayed the same.
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
it('should fire change for checkbox input', () => {
|
||||
var called = 0;
|
||||
|
||||
@@ -78,12 +151,6 @@ describe('ChangeEventPlugin', () => {
|
||||
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
|
||||
@@ -107,6 +174,30 @@ describe('ChangeEventPlugin', () => {
|
||||
expect(called).toBe(1);
|
||||
});
|
||||
|
||||
it('should not distinguish equal string and number values', () => {
|
||||
var called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
var input = ReactDOM.render(
|
||||
<input type="text" defaultValue="42" onChange={cb} />,
|
||||
container,
|
||||
);
|
||||
|
||||
// When we set `value` as a property, React updates the "current" value
|
||||
// that it tracks internally. The "current" value is later used to determine
|
||||
// whether a change event is a duplicate or not.
|
||||
// Even though we set value to a number, we still shouldn't get a change
|
||||
// event because as a string, it's equal to the initial value ('42').
|
||||
input.value = 42;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
|
||||
expect(input.value).toBe('42');
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
// See a similar input test above for a detailed description of why.
|
||||
it('should not fire change when setting checked programmatically', () => {
|
||||
var called = 0;
|
||||
@@ -165,6 +256,53 @@ describe('ChangeEventPlugin', () => {
|
||||
expect(called).toBe(1);
|
||||
});
|
||||
|
||||
it('should track radio button cousins in a group', () => {
|
||||
var called1 = 0;
|
||||
var called2 = 0;
|
||||
|
||||
function cb1(e) {
|
||||
called1++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
function cb2(e) {
|
||||
called2++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
var div = ReactDOM.render(
|
||||
<div>
|
||||
<input type="radio" name="group" onChange={cb1} />
|
||||
<input type="radio" name="group" onChange={cb2} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
var option1 = div.childNodes[0];
|
||||
var option2 = div.childNodes[1];
|
||||
|
||||
// Select first option.
|
||||
option1.dispatchEvent(
|
||||
new Event('click', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
expect(called1).toBe(1);
|
||||
expect(called2).toBe(0);
|
||||
|
||||
// Select second option.
|
||||
option2.dispatchEvent(
|
||||
new Event('click', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
expect(called1).toBe(1);
|
||||
expect(called2).toBe(1);
|
||||
|
||||
// Select the first option.
|
||||
// It should receive the React change event again.
|
||||
option1.dispatchEvent(
|
||||
new Event('click', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
expect(called1).toBe(2);
|
||||
expect(called2).toBe(1);
|
||||
});
|
||||
|
||||
it('should deduplicate input value change events', () => {
|
||||
var called = 0;
|
||||
|
||||
@@ -177,10 +315,15 @@ describe('ChangeEventPlugin', () => {
|
||||
['text', 'number', 'range'].forEach(type => {
|
||||
called = 0;
|
||||
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
setUntrackedValue.call(input, '42');
|
||||
input.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
@@ -189,10 +332,15 @@ describe('ChangeEventPlugin', () => {
|
||||
|
||||
called = 0;
|
||||
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
setUntrackedValue.call(input, '42');
|
||||
input.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
@@ -201,10 +349,15 @@ describe('ChangeEventPlugin', () => {
|
||||
|
||||
called = 0;
|
||||
input = ReactDOM.render(<input type={type} onChange={cb} />, container);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
setUntrackedValue.call(input, '42');
|
||||
input.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// Should be ignored (no change):
|
||||
input.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
@@ -257,4 +410,32 @@ describe('ChangeEventPlugin', () => {
|
||||
|
||||
expect(called).toBe(2);
|
||||
});
|
||||
|
||||
it('does not crash for nodes with custom value property', () => {
|
||||
// https://github.com/facebook/react/issues/10196
|
||||
try {
|
||||
var originalCreateElement = document.createElement;
|
||||
document.createElement = function() {
|
||||
var node = originalCreateElement.apply(this, arguments);
|
||||
Object.defineProperty(node, 'value', {
|
||||
get() {},
|
||||
set() {},
|
||||
});
|
||||
return node;
|
||||
};
|
||||
var div = document.createElement('div');
|
||||
// Mount
|
||||
var node = ReactDOM.render(<input type="text" />, div);
|
||||
// Update
|
||||
ReactDOM.render(<input type="text" />, div);
|
||||
// Change
|
||||
node.dispatchEvent(
|
||||
new Event('change', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
// Unmount
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
} finally {
|
||||
document.createElement = originalCreateElement;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user