@samitouri / QOS-React / commits / f0ba6bbf20

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

Dan Abramov committed Nov 25, 2017 at 01:47 UTC f0ba6bbf200e301e97c3170f42f12bd2d20fd967
3 files changed +187 -192
packages/react-dom/src/__tests__/inputValueTracking-test.js deleted
-181
@@ -1,181 +0,0 @@
1 -/**
2 - * Copyright (c) 2013-present, Facebook, Inc.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -'use strict';
11 -
12 -var React = require('react');
13 -var ReactDOM = require('react-dom');
14 -var ReactTestUtils = require('react-dom/test-utils');
15 -// TODO: can we express this test with only public API?
16 -var inputValueTracking = require('../client/inputValueTracking');
17 -
18 -var getTracker = inputValueTracking._getTrackerFromNode;
19 -
20 -describe('inputValueTracking', () => {
21 - var input;
22 -
23 - beforeEach(() => {
24 - input = document.createElement('input');
25 - input.type = 'text';
26 - });
27 -
28 - it('should attach tracker to node', () => {
29 - var node = ReactTestUtils.renderIntoDocument(<input type="text" />);
30 -
31 - expect(getTracker(node)).toBeDefined();
32 - });
33 -
34 - it('should define `value` on the node instance', () => {
35 - var node = ReactTestUtils.renderIntoDocument(<input type="text" />);
36 -
37 - expect(node.hasOwnProperty('value')).toBe(true);
38 - });
39 -
40 - it('should define `checked` on the node instance', () => {
41 - var node = ReactTestUtils.renderIntoDocument(<input type="checkbox" />);
42 -
43 - expect(node.hasOwnProperty('checked')).toBe(true);
44 - });
45 -
46 - it('should initialize with the current value', () => {
47 - input.value = 'foo';
48 -
49 - inputValueTracking.track(input);
50 -
51 - var tracker = getTracker(input);
52 -
53 - expect(tracker.getValue()).toEqual('foo');
54 - });
55 -
56 - it('should initialize with the current `checked`', () => {
57 - const checkbox = document.createElement('input');
58 -
59 - checkbox.type = 'checkbox';
60 - checkbox.checked = true;
61 - inputValueTracking.track(checkbox);
62 -
63 - var tracker = getTracker(checkbox);
64 -
65 - expect(tracker.getValue()).toEqual('true');
66 - });
67 -
68 - it('should track value changes', () => {
69 - var node = ReactTestUtils.renderIntoDocument(
70 - <input type="text" defaultValue="foo" />,
71 - );
72 -
73 - var tracker = getTracker(node);
74 -
75 - node.value = 'bar';
76 - expect(tracker.getValue()).toEqual('bar');
77 - });
78 -
79 - it('should tracked`checked` changes', () => {
80 - var node = ReactTestUtils.renderIntoDocument(
81 - <input type="checkbox" defaultChecked={true} />,
82 - );
83 -
84 - var tracker = getTracker(node);
85 -
86 - node.checked = false;
87 - expect(tracker.getValue()).toEqual('false');
88 - });
89 -
90 - it('should update value manually', () => {
91 - var node = ReactTestUtils.renderIntoDocument(
92 - <input type="text" defaultValue="foo" />,
93 - );
94 -
95 - var tracker = getTracker(node);
96 -
97 - tracker.setValue('bar');
98 - expect(tracker.getValue()).toEqual('bar');
99 - });
100 -
101 - it('should coerce value to a string', () => {
102 - var node = ReactTestUtils.renderIntoDocument(
103 - <input type="text" defaultValue="foo" />,
104 - );
105 -
106 - var tracker = getTracker(node);
107 -
108 - tracker.setValue(500);
109 - expect(tracker.getValue()).toEqual('500');
110 - });
111 -
112 - it('should update value if it changed and return result', () => {
113 - var node = ReactTestUtils.renderIntoDocument(
114 - <input type="text" defaultValue="foo" />,
115 - );
116 -
117 - var tracker = getTracker(node);
118 -
119 - expect(inputValueTracking.updateValueIfChanged(node)).toBe(false);
120 -
121 - tracker.setValue('bar');
122 -
123 - expect(inputValueTracking.updateValueIfChanged(node)).toBe(true);
124 -
125 - expect(tracker.getValue()).toEqual('foo');
126 - });
127 -
128 - it('should return true when updating untracked instance', () => {
129 - input.value = 'foo';
130 -
131 - expect(inputValueTracking.updateValueIfChanged(input)).toBe(true);
132 -
133 - expect(getTracker(input)).not.toBeDefined();
134 - });
135 -
136 - it('should return tracker from node', () => {
137 - var div = document.createElement('div');
138 - var node = ReactDOM.render(<input type="text" defaultValue="foo" />, div);
139 -
140 - var tracker = getTracker(node);
141 - expect(tracker.getValue()).toEqual('foo');
142 - });
143 -
144 - it('should stop tracking', () => {
145 - inputValueTracking.track(input);
146 -
147 - expect(getTracker(input)).not.toEqual(null);
148 -
149 - inputValueTracking.stopTracking(input);
150 -
151 - expect(getTracker(input)).toEqual(null);
152 -
153 - expect(input.hasOwnProperty('value')).toBe(false);
154 - });
155 -
156 - it('does not crash for nodes with custom value property', () => {
157 - // https://github.com/facebook/react/issues/10196
158 - try {
159 - var originalCreateElement = document.createElement;
160 - document.createElement = function() {
161 - var node = originalCreateElement.apply(this, arguments);
162 - Object.defineProperty(node, 'value', {
163 - get() {},
164 - set() {},
165 - });
166 - return node;
167 - };
168 - var div = document.createElement('div');
169 - // Mount
170 - var node = ReactDOM.render(<input type="text" />, div);
171 - // Update
172 - ReactDOM.render(<input type="text" />, div);
173 - // Change
174 - ReactTestUtils.SimulateNative.change(node);
175 - // Unmount
176 - ReactDOM.unmountComponentAtNode(div);
177 - } finally {
178 - document.createElement = originalCreateElement;
179 - }
180 - });
181 -});
packages/react-dom/src/client/inputValueTracking.js
-5
@@ -96,11 +96,6 @@ function trackValueOnNode(node: any): ?ValueTracker {
96 return tracker;
97 }
98
99 -export {
100 - // exposed for testing
101 - getTracker as _getTrackerFromNode,
102 -};
103 -
99 export function track(node: ElementWithValueTracker) {
100 if (getTracker(node)) {
101 return;
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.js
+187 -6
@@ -35,6 +35,79 @@ describe('ChangeEventPlugin', () => {
35 container = null;
36 });
37
38 + // We try to avoid firing "duplicate" React change events.
39 + // However, to tell which events are "duplicates" and should be ignored,
40 + // we are tracking the "current" input value, and only respect events
41 + // that occur after it changes. In most of these tests, we verify that we
42 + // keep track of the "current" value and only fire events when it changes.
43 + // See https://github.com/facebook/react/pull/5746.
44 +
45 + it('should consider initial text value to be current', () => {
46 + var called = 0;
47 +
48 + function cb(e) {
49 + called++;
50 + expect(e.type).toBe('change');
51 + }
52 +
53 + var node = ReactDOM.render(
54 + <input type="text" onChange={cb} defaultValue="foo" />,
55 + container,
56 + );
57 + node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
58 + node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
59 + // There should be no React change events because the value stayed the same.
60 + expect(called).toBe(0);
61 + });
62 +
63 + it('should consider initial checkbox checked=true to be current', () => {
64 + var called = 0;
65 +
66 + function cb(e) {
67 + called++;
68 + expect(e.type).toBe('change');
69 + }
70 +
71 + var node = ReactDOM.render(
72 + <input type="checkbox" onChange={cb} defaultChecked={true} />,
73 + container,
74 + );
75 +
76 + // Secretly, set `checked` to false, so that dispatching the `click` will
77 + // make it `true` again. Thus, at the time of the event, React should not
78 + // consider it a change from the initial `true` value.
79 + setUntrackedChecked.call(node, false);
80 + node.dispatchEvent(
81 + new MouseEvent('click', {bubbles: true, cancelable: true}),
82 + );
83 + // There should be no React change events because the value stayed the same.
84 + expect(called).toBe(0);
85 + });
86 +
87 + it('should consider initial checkbox checked=false to be current', () => {
88 + var called = 0;
89 +
90 + function cb(e) {
91 + called++;
92 + expect(e.type).toBe('change');
93 + }
94 +
95 + var node = ReactDOM.render(
96 + <input type="checkbox" onChange={cb} defaultChecked={false} />,
97 + container,
98 + );
99 +
100 + // Secretly, set `checked` to true, so that dispatching the `click` will
101 + // make it `false` again. Thus, at the time of the event, React should not
102 + // consider it a change from the initial `false` value.
103 + setUntrackedChecked.call(node, true);
104 + node.dispatchEvent(
105 + new MouseEvent('click', {bubbles: true, cancelable: true}),
106 + );
107 + // There should be no React change events because the value stayed the same.
108 + expect(called).toBe(0);
109 + });
110 +
111 it('should fire change for checkbox input', () => {
112 var called = 0;
113
@@ -78,12 +151,6 @@ describe('ChangeEventPlugin', () => {
151 container,
152 );
153
81 - // We try to avoid firing "duplicate" React change events.
82 - // However, to tell which events are duplicates and should be ignored,
83 - // we are tracking the "current" input value, and only respect events
84 - // that occur after it changes. In this test, we verify that we can
85 - // keep track of the "current" value even if it is set programatically.
86 -
154 // Set it programmatically.
155 input.value = 'bar';
156 // Even if a DOM input event fires, React sees that the real input value now
@@ -107,6 +174,30 @@ describe('ChangeEventPlugin', () => {
174 expect(called).toBe(1);
175 });
176
177 + it('should not distinguish equal string and number values', () => {
178 + var called = 0;
179 +
180 + function cb(e) {
181 + called++;
182 + expect(e.type).toBe('change');
183 + }
184 +
185 + var input = ReactDOM.render(
186 + <input type="text" defaultValue="42" onChange={cb} />,
187 + container,
188 + );
189 +
190 + // When we set `value` as a property, React updates the "current" value
191 + // that it tracks internally. The "current" value is later used to determine
192 + // whether a change event is a duplicate or not.
193 + // Even though we set value to a number, we still shouldn't get a change
194 + // event because as a string, it's equal to the initial value ('42').
195 + input.value = 42;
196 + input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
197 + expect(input.value).toBe('42');
198 + expect(called).toBe(0);
199 + });
200 +
201 // See a similar input test above for a detailed description of why.
202 it('should not fire change when setting checked programmatically', () => {
203 var called = 0;
@@ -165,6 +256,53 @@ describe('ChangeEventPlugin', () => {
256 expect(called).toBe(1);
257 });
258
259 + it('should track radio button cousins in a group', () => {
260 + var called1 = 0;
261 + var called2 = 0;
262 +
263 + function cb1(e) {
264 + called1++;
265 + expect(e.type).toBe('change');
266 + }
267 +
268 + function cb2(e) {
269 + called2++;
270 + expect(e.type).toBe('change');
271 + }
272 +
273 + var div = ReactDOM.render(
274 + <div>
275 + <input type="radio" name="group" onChange={cb1} />
276 + <input type="radio" name="group" onChange={cb2} />
277 + </div>,
278 + container,
279 + );
280 + var option1 = div.childNodes[0];
281 + var option2 = div.childNodes[1];
282 +
283 + // Select first option.
284 + option1.dispatchEvent(
285 + new Event('click', {bubbles: true, cancelable: true}),
286 + );
287 + expect(called1).toBe(1);
288 + expect(called2).toBe(0);
289 +
290 + // Select second option.
291 + option2.dispatchEvent(
292 + new Event('click', {bubbles: true, cancelable: true}),
293 + );
294 + expect(called1).toBe(1);
295 + expect(called2).toBe(1);
296 +
297 + // Select the first option.
298 + // It should receive the React change event again.
299 + option1.dispatchEvent(
300 + new Event('click', {bubbles: true, cancelable: true}),
301 + );
302 + expect(called1).toBe(2);
303 + expect(called2).toBe(1);
304 + });
305 +
306 it('should deduplicate input value change events', () => {
307 var called = 0;
308
@@ -177,10 +315,15 @@ describe('ChangeEventPlugin', () => {
315 ['text', 'number', 'range'].forEach(type => {
316 called = 0;
317 input = ReactDOM.render(<input type={type} onChange={cb} />, container);
318 + // Should be ignored (no change):
319 + input.dispatchEvent(
320 + new Event('change', {bubbles: true, cancelable: true}),
321 + );
322 setUntrackedValue.call(input, '42');
323 input.dispatchEvent(
324 new Event('change', {bubbles: true, cancelable: true}),
325 );
326 + // Should be ignored (no change):
327 input.dispatchEvent(
328 new Event('change', {bubbles: true, cancelable: true}),
329 );
@@ -189,10 +332,15 @@ describe('ChangeEventPlugin', () => {
332
333 called = 0;
334 input = ReactDOM.render(<input type={type} onChange={cb} />, container);
335 + // Should be ignored (no change):
336 + input.dispatchEvent(
337 + new Event('input', {bubbles: true, cancelable: true}),
338 + );
339 setUntrackedValue.call(input, '42');
340 input.dispatchEvent(
341 new Event('input', {bubbles: true, cancelable: true}),
342 );
343 + // Should be ignored (no change):
344 input.dispatchEvent(
345 new Event('input', {bubbles: true, cancelable: true}),
346 );
@@ -201,10 +349,15 @@ describe('ChangeEventPlugin', () => {
349
350 called = 0;
351 input = ReactDOM.render(<input type={type} onChange={cb} />, container);
352 + // Should be ignored (no change):
353 + input.dispatchEvent(
354 + new Event('change', {bubbles: true, cancelable: true}),
355 + );
356 setUntrackedValue.call(input, '42');
357 input.dispatchEvent(
358 new Event('input', {bubbles: true, cancelable: true}),
359 );
360 + // Should be ignored (no change):
361 input.dispatchEvent(
362 new Event('change', {bubbles: true, cancelable: true}),
363 );
@@ -257,4 +410,32 @@ describe('ChangeEventPlugin', () => {
410
411 expect(called).toBe(2);
412 });
413 +
414 + it('does not crash for nodes with custom value property', () => {
415 + // https://github.com/facebook/react/issues/10196
416 + try {
417 + var originalCreateElement = document.createElement;
418 + document.createElement = function() {
419 + var node = originalCreateElement.apply(this, arguments);
420 + Object.defineProperty(node, 'value', {
421 + get() {},
422 + set() {},
423 + });
424 + return node;
425 + };
426 + var div = document.createElement('div');
427 + // Mount
428 + var node = ReactDOM.render(<input type="text" />, div);
429 + // Update
430 + ReactDOM.render(<input type="text" />, div);
431 + // Change
432 + node.dispatchEvent(
433 + new Event('change', {bubbles: true, cancelable: true}),
434 + );
435 + // Unmount
436 + ReactDOM.unmountComponentAtNode(div);
437 + } finally {
438 + document.createElement = originalCreateElement;
439 + }
440 + });
441 });