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
});