Replace inputValueTracking-test with public API tests (#11654)

This commit is contained in:
Dan Abramov
2017-11-25 01:47:10 +00:00
committed by GitHub
parent a67757e115
commit f0ba6bbf20
3 changed files with 187 additions and 192 deletions
@@ -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;
}
});
});
-5
View File
@@ -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;
}
});
});