@samitouri / QOS-React / commits / a67757e115

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