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
Ethan Arrowood committed
Nov 24, 2017 at 17:18 UTC
a67757e11540936677b0a5d89d623b3e38b5fe69
2 files changed
+260
-222
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
deleted
-222
@@ -1,222 +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 ChangeEventPlugin = require('../ChangeEventPlugin').default;
17
-var inputValueTracking = require('../../client/inputValueTracking');
18
-
19
-function getTrackedValue(elem) {
20
- var tracker = inputValueTracking._getTrackerFromNode(elem);
21
- return tracker.getValue();
22
-}
23
-
24
-function setTrackedValue(elem, value) {
25
- var tracker = inputValueTracking._getTrackerFromNode(elem);
26
- tracker.setValue(value);
27
-}
28
-
29
-function setUntrackedValue(elem, value) {
30
- var tracker = inputValueTracking._getTrackerFromNode(elem);
31
- var current = tracker.getValue();
32
-
33
- if (elem.type === 'checkbox' || elem.type === 'radio') {
34
- elem.checked = value;
35
- } else {
36
- elem.value = value;
37
- }
38
- tracker.setValue(current);
39
-}
40
-
41
-describe('ChangeEventPlugin', () => {
42
- it('should fire change for checkbox input', () => {
43
- var called = 0;
44
-
45
- function cb(e) {
46
- called = 1;
47
- expect(e.type).toBe('change');
48
- }
49
-
50
- var input = ReactTestUtils.renderIntoDocument(
51
- <input type="checkbox" onChange={cb} />,
52
- );
53
-
54
- setUntrackedValue(input, true);
55
- ReactTestUtils.SimulateNative.click(input);
56
-
57
- expect(called).toBe(1);
58
- });
59
-
60
- it('should catch setting the value programmatically', () => {
61
- var input = ReactTestUtils.renderIntoDocument(
62
- <input type="text" defaultValue="foo" />,
63
- );
64
-
65
- input.value = 'bar';
66
- expect(getTrackedValue(input)).toBe('bar');
67
- });
68
-
69
- it('should not fire change when setting the value programmatically', () => {
70
- var called = 0;
71
-
72
- function cb(e) {
73
- called += 1;
74
- expect(e.type).toBe('change');
75
- }
76
-
77
- var input = ReactTestUtils.renderIntoDocument(
78
- <input type="text" onChange={cb} defaultValue="foo" />,
79
- );
80
-
81
- input.value = 'bar';
82
- ReactTestUtils.SimulateNative.change(input);
83
- expect(called).toBe(0);
84
-
85
- setUntrackedValue(input, 'foo');
86
- ReactTestUtils.SimulateNative.change(input);
87
-
88
- expect(called).toBe(1);
89
- });
90
-
91
- it('should not fire change when setting checked programmatically', () => {
92
- var called = 0;
93
-
94
- function cb(e) {
95
- called += 1;
96
- expect(e.type).toBe('change');
97
- }
98
-
99
- var input = ReactTestUtils.renderIntoDocument(
100
- <input type="checkbox" onChange={cb} defaultChecked={true} />,
101
- );
102
-
103
- input.checked = true;
104
- ReactTestUtils.SimulateNative.click(input);
105
- expect(called).toBe(0);
106
-
107
- input.checked = false;
108
- setTrackedValue(input, undefined);
109
- ReactTestUtils.SimulateNative.click(input);
110
-
111
- expect(called).toBe(1);
112
- });
113
-
114
- it('should unmount', () => {
115
- var container = document.createElement('div');
116
- var input = ReactDOM.render(<input />, container);
117
-
118
- ReactDOM.unmountComponentAtNode(container);
119
- });
120
-
121
- it('should only fire change for checked radio button once', () => {
122
- var called = 0;
123
-
124
- function cb(e) {
125
- called += 1;
126
- }
127
-
128
- var input = ReactTestUtils.renderIntoDocument(
129
- <input type="radio" onChange={cb} />,
130
- );
131
- setUntrackedValue(input, true);
132
- ReactTestUtils.SimulateNative.click(input);
133
- ReactTestUtils.SimulateNative.click(input);
134
- expect(called).toBe(1);
135
- });
136
-
137
- it('should deduplicate input value change events', () => {
138
- var input;
139
- var called = 0;
140
-
141
- function cb(e) {
142
- called += 1;
143
- expect(e.type).toBe('change');
144
- }
145
-
146
- [
147
- <input type="text" onChange={cb} />,
148
- <input type="number" onChange={cb} />,
149
- <input type="range" onChange={cb} />,
150
- ].forEach(function(element) {
151
- called = 0;
152
- input = ReactTestUtils.renderIntoDocument(element);
153
-
154
- setUntrackedValue(input, '40');
155
- ReactTestUtils.SimulateNative.change(input);
156
- ReactTestUtils.SimulateNative.change(input);
157
- expect(called).toBe(1);
158
-
159
- called = 0;
160
- input = ReactTestUtils.renderIntoDocument(element);
161
- setUntrackedValue(input, '40');
162
- ReactTestUtils.SimulateNative.input(input);
163
- ReactTestUtils.SimulateNative.input(input);
164
- expect(called).toBe(1);
165
-
166
- called = 0;
167
- input = ReactTestUtils.renderIntoDocument(element);
168
- setUntrackedValue(input, '40');
169
- ReactTestUtils.SimulateNative.input(input);
170
- ReactTestUtils.SimulateNative.change(input);
171
- expect(called).toBe(1);
172
- });
173
- });
174
-
175
- it('should listen for both change and input events when supported', () => {
176
- var called = 0;
177
-
178
- function cb(e) {
179
- called += 1;
180
- expect(e.type).toBe('change');
181
- }
182
-
183
- if (!ChangeEventPlugin._isInputEventSupported) {
184
- return;
185
- }
186
-
187
- var input = ReactTestUtils.renderIntoDocument(
188
- <input type="range" onChange={cb} />,
189
- );
190
- setUntrackedValue(input, 'bar');
191
-
192
- ReactTestUtils.SimulateNative.input(input);
193
-
194
- setUntrackedValue(input, 'foo');
195
-
196
- ReactTestUtils.SimulateNative.change(input);
197
-
198
- expect(called).toBe(2);
199
- });
200
-
201
- it('should only fire events when the value changes for range inputs', () => {
202
- var called = 0;
203
-
204
- function cb(e) {
205
- called += 1;
206
- expect(e.type).toBe('change');
207
- }
208
-
209
- var input = ReactTestUtils.renderIntoDocument(
210
- <input type="range" onChange={cb} />,
211
- );
212
- setUntrackedValue(input, '40');
213
- ReactTestUtils.SimulateNative.input(input);
214
- ReactTestUtils.SimulateNative.change(input);
215
-
216
- setUntrackedValue(input, 'foo');
217
-
218
- ReactTestUtils.SimulateNative.input(input);
219
- ReactTestUtils.SimulateNative.change(input);
220
- expect(called).toBe(2);
221
- });
222
-});
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.js
new
+260
@@ -0,0 +1,260 @@
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
+
15
+var setUntrackedChecked = Object.getOwnPropertyDescriptor(
16
+ HTMLInputElement.prototype,
17
+ 'checked',
18
+).set;
19
+
20
+var setUntrackedValue = Object.getOwnPropertyDescriptor(
21
+ HTMLInputElement.prototype,
22
+ 'value',
23
+).set;
24
+
25
+describe('ChangeEventPlugin', () => {
26
+ var container;
27
+
28
+ beforeEach(() => {
29
+ container = document.createElement('div');
30
+ document.body.appendChild(container);
31
+ });
32
+
33
+ afterEach(() => {
34
+ document.body.removeChild(container);
35
+ container = null;
36
+ });
37
+
38
+ it('should fire change for checkbox input', () => {
39
+ var called = 0;
40
+
41
+ function cb(e) {
42
+ called++;
43
+ expect(e.type).toBe('change');
44
+ }
45
+
46
+ var node = ReactDOM.render(
47
+ <input type="checkbox" onChange={cb} />,
48
+ container,
49
+ );
50
+
51
+ expect(node.checked).toBe(false);
52
+ node.dispatchEvent(
53
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
54
+ );
55
+ // Note: unlike with text input events, dispatching `click` actually
56
+ // toggles the checkbox and updates its `checked` value.
57
+ expect(node.checked).toBe(true);
58
+ expect(called).toBe(1);
59
+
60
+ expect(node.checked).toBe(true);
61
+ node.dispatchEvent(
62
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
63
+ );
64
+ expect(node.checked).toBe(false);
65
+ expect(called).toBe(2);
66
+ });
67
+
68
+ it('should not fire change setting the value programmatically', () => {
69
+ var called = 0;
70
+
71
+ function cb(e) {
72
+ called++;
73
+ expect(e.type).toBe('change');
74
+ }
75
+
76
+ var input = ReactDOM.render(
77
+ <input type="text" defaultValue="foo" onChange={cb} />,
78
+ container,
79
+ );
80
+
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
+
87
+ // Set it programmatically.
88
+ input.value = 'bar';
89
+ // Even if a DOM input event fires, React sees that the real input value now
90
+ // ('bar') is the same as the "current" one we already recorded.
91
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
92
+ expect(input.value).toBe('bar');
93
+ // In this case we don't expect to get a React event.
94
+ expect(called).toBe(0);
95
+
96
+ // However, we can simulate user typing by calling the underlying setter.
97
+ setUntrackedValue.call(input, 'foo');
98
+ // Now, when the event fires, the real input value ('foo') differs from the
99
+ // "current" one we previously recorded ('bar').
100
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
101
+ expect(input.value).toBe('foo');
102
+ // In this case React should fire an event for it.
103
+ expect(called).toBe(1);
104
+
105
+ // Verify again that extra events without real changes are ignored.
106
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
107
+ expect(called).toBe(1);
108
+ });
109
+
110
+ // See a similar input test above for a detailed description of why.
111
+ it('should not fire change when setting checked programmatically', () => {
112
+ var called = 0;
113
+
114
+ function cb(e) {
115
+ called++;
116
+ expect(e.type).toBe('change');
117
+ }
118
+
119
+ var input = ReactDOM.render(
120
+ <input type="checkbox" onChange={cb} defaultChecked={false} />,
121
+ container,
122
+ );
123
+
124
+ // Set the value, updating the "current" value that React tracks to true.
125
+ input.checked = true;
126
+ // Under the hood, uncheck the box so that the click will "check" it again.
127
+ setUntrackedChecked.call(input, false);
128
+ input.dispatchEvent(
129
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
130
+ );
131
+ expect(input.checked).toBe(true);
132
+ // We don't expect a React event because at the time of the click, the real
133
+ // checked value (true) was the same as the last recorded "current" value
134
+ // (also true).
135
+ expect(called).toBe(0);
136
+
137
+ // However, simulating a normal click should fire a React event because the
138
+ // real value (false) would have changed from the last tracked value (true).
139
+ input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
140
+ expect(called).toBe(1);
141
+ });
142
+
143
+ it('should unmount', () => {
144
+ var input = ReactDOM.render(<input />, container);
145
+
146
+ ReactDOM.unmountComponentAtNode(container);
147
+ });
148
+
149
+ it('should only fire change for checked radio button once', () => {
150
+ var called = 0;
151
+
152
+ function cb(e) {
153
+ called++;
154
+ expect(e.type).toBe('change');
155
+ }
156
+
157
+ var input = ReactDOM.render(
158
+ <input type="radio" onChange={cb} />,
159
+ container,
160
+ );
161
+
162
+ setUntrackedChecked.call(input, true);
163
+ input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
164
+ input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
165
+ expect(called).toBe(1);
166
+ });
167
+
168
+ it('should deduplicate input value change events', () => {
169
+ var called = 0;
170
+
171
+ function cb(e) {
172
+ called++;
173
+ expect(e.type).toBe('change');
174
+ }
175
+
176
+ var input;
177
+ ['text', 'number', 'range'].forEach(type => {
178
+ called = 0;
179
+ input = ReactDOM.render(<input type={type} onChange={cb} />, container);
180
+ setUntrackedValue.call(input, '42');
181
+ input.dispatchEvent(
182
+ new Event('change', {bubbles: true, cancelable: true}),
183
+ );
184
+ input.dispatchEvent(
185
+ new Event('change', {bubbles: true, cancelable: true}),
186
+ );
187
+ expect(called).toBe(1);
188
+ ReactDOM.unmountComponentAtNode(container);
189
+
190
+ called = 0;
191
+ input = ReactDOM.render(<input type={type} onChange={cb} />, container);
192
+ setUntrackedValue.call(input, '42');
193
+ input.dispatchEvent(
194
+ new Event('input', {bubbles: true, cancelable: true}),
195
+ );
196
+ input.dispatchEvent(
197
+ new Event('input', {bubbles: true, cancelable: true}),
198
+ );
199
+ expect(called).toBe(1);
200
+ ReactDOM.unmountComponentAtNode(container);
201
+
202
+ called = 0;
203
+ input = ReactDOM.render(<input type={type} onChange={cb} />, container);
204
+ setUntrackedValue.call(input, '42');
205
+ input.dispatchEvent(
206
+ new Event('input', {bubbles: true, cancelable: true}),
207
+ );
208
+ input.dispatchEvent(
209
+ new Event('change', {bubbles: true, cancelable: true}),
210
+ );
211
+ expect(called).toBe(1);
212
+ ReactDOM.unmountComponentAtNode(container);
213
+ });
214
+ });
215
+
216
+ it('should listen for both change and input events when supported', () => {
217
+ var called = 0;
218
+
219
+ function cb(e) {
220
+ called++;
221
+ expect(e.type).toBe('change');
222
+ }
223
+
224
+ var input = ReactDOM.render(
225
+ <input type="range" onChange={cb} />,
226
+ container,
227
+ );
228
+
229
+ setUntrackedValue.call(input, 'foo');
230
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
231
+
232
+ setUntrackedValue.call(input, 'bar');
233
+ input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
234
+
235
+ expect(called).toBe(2);
236
+ });
237
+
238
+ it('should only fire events when the value changes for range inputs', () => {
239
+ var called = 0;
240
+
241
+ function cb(e) {
242
+ called++;
243
+ expect(e.type).toBe('change');
244
+ }
245
+
246
+ var input = ReactDOM.render(
247
+ <input type="range" onChange={cb} />,
248
+ container,
249
+ );
250
+ setUntrackedValue.call(input, '40');
251
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
252
+ input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
253
+
254
+ setUntrackedValue.call(input, 'foo');
255
+ input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
256
+ input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
257
+
258
+ expect(called).toBe(2);
259
+ });
260
+});