Rewrite DOMPropertyOperations-test in terms of public API (#10281)
* Rewrite DOMPropertyOperations-test in terms of public API * Add more assertions I don't understand what the jsdom comments are about since they pass. * Address review feedback
Dan Abramov committed
Aug 7, 2017 at 20:12 UTC
4e4653da9b9aa44651d202e5ce4e4b340234bdd8
1 file changed
+97
-187
src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js
+97
-187
@@ -12,67 +12,54 @@
12
'use strict';
13
14
describe('DOMPropertyOperations', () => {
15
- var DOMPropertyOperations;
16
- var DOMProperty;
17
- var ReactDOMComponentTree;
15
+ var React;
16
+ var ReactDOM;
17
18
beforeEach(() => {
19
jest.resetModules();
21
- require('ReactDOMInjection');
22
-
23
- // TODO: can we express this test with only public API?
24
- DOMPropertyOperations = require('DOMPropertyOperations');
25
- DOMProperty = require('DOMProperty');
26
- ReactDOMComponentTree = require('ReactDOMComponentTree');
20
+ React = require('react');
21
+ ReactDOM = require('react-dom');
22
});
23
24
describe('setValueForProperty', () => {
30
- var stubNode;
31
- var stubInstance;
32
-
33
- beforeEach(() => {
34
- stubNode = document.createElement('div');
35
- stubInstance = {_debugID: 1};
36
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
37
- });
38
-
25
it('should set values as properties by default', () => {
40
- DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'Tip!');
41
- expect(stubNode.title).toBe('Tip!');
26
+ var container = document.createElement('div');
27
+ ReactDOM.render(<div title="Tip!" />, container);
28
+ expect(container.firstChild.title).toBe('Tip!');
29
});
30
31
it('should set values as attributes if necessary', () => {
45
- DOMPropertyOperations.setValueForProperty(stubNode, 'role', '#');
46
- expect(stubNode.getAttribute('role')).toBe('#');
47
- expect(stubNode.role).toBeUndefined();
32
+ var container = document.createElement('div');
33
+ ReactDOM.render(<div role="#" />, container);
34
+ expect(container.firstChild.getAttribute('role')).toBe('#');
35
+ expect(container.firstChild.role).toBeUndefined();
36
});
37
38
it('should set values as namespace attributes if necessary', () => {
51
- spyOn(stubNode, 'setAttributeNS');
52
- DOMPropertyOperations.setValueForProperty(
53
- stubNode,
54
- 'xlinkHref',
55
- 'about:blank',
56
- );
57
- expect(stubNode.setAttributeNS.calls.count()).toBe(1);
58
- expect(stubNode.setAttributeNS.calls.argsFor(0)).toEqual([
59
- 'http://www.w3.org/1999/xlink',
60
- 'xlink:href',
61
- 'about:blank',
62
- ]);
39
+ var container = document.createElement('svg');
40
+ ReactDOM.render(<image xlinkHref="about:blank" />, container);
41
+ expect(
42
+ container.firstChild.getAttributeNS(
43
+ 'http://www.w3.org/1999/xlink',
44
+ 'href',
45
+ ),
46
+ ).toBe('about:blank');
47
});
48
49
it('should set values as boolean properties', () => {
66
- DOMPropertyOperations.setValueForProperty(
67
- stubNode,
68
- 'disabled',
69
- 'disabled',
70
- );
71
- expect(stubNode.getAttribute('disabled')).toBe('');
72
- DOMPropertyOperations.setValueForProperty(stubNode, 'disabled', true);
73
- expect(stubNode.getAttribute('disabled')).toBe('');
74
- DOMPropertyOperations.setValueForProperty(stubNode, 'disabled', false);
75
- expect(stubNode.getAttribute('disabled')).toBe(null);
50
+ var container = document.createElement('div');
51
+ ReactDOM.render(<div disabled="disabled" />, container);
52
+ expect(container.firstChild.getAttribute('disabled')).toBe('');
53
+ ReactDOM.render(<div disabled={true} />, container);
54
+ expect(container.firstChild.getAttribute('disabled')).toBe('');
55
+ ReactDOM.render(<div disabled={false} />, container);
56
+ expect(container.firstChild.getAttribute('disabled')).toBe(null);
57
+ ReactDOM.render(<div disabled={true} />, container);
58
+ ReactDOM.render(<div disabled={null} />, container);
59
+ expect(container.firstChild.getAttribute('disabled')).toBe(null);
60
+ ReactDOM.render(<div disabled={true} />, container);
61
+ ReactDOM.render(<div disabled={undefined} />, container);
62
+ expect(container.firstChild.getAttribute('disabled')).toBe(null);
63
});
64
65
it('should convert attribute values to string first', () => {
@@ -83,181 +70,104 @@ describe('DOMPropertyOperations', () => {
70
return '<html>';
71
},
72
};
86
- DOMPropertyOperations.setValueForProperty(stubNode, 'role', obj);
87
- expect(stubNode.getAttribute('role')).toBe('<html>');
73
+ var container = document.createElement('div');
74
+ ReactDOM.render(<div role={obj} />, container);
75
+ expect(container.firstChild.getAttribute('role')).toBe('<html>');
76
});
77
78
it('should not remove empty attributes for special properties', () => {
91
- stubNode = document.createElement('input');
92
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
93
-
94
- DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
95
- // JSDOM does not behave correctly for attributes/properties
96
- //expect(stubNode.getAttribute('value')).toBe('');
97
- expect(stubNode.value).toBe('');
79
+ var container = document.createElement('div');
80
+ ReactDOM.render(<input value="" />, container);
81
+ expect(container.firstChild.getAttribute('value')).toBe('');
82
+ expect(container.firstChild.value).toBe('');
83
});
84
85
it('should remove for falsey boolean properties', () => {
101
- DOMPropertyOperations.setValueForProperty(
102
- stubNode,
103
- 'allowFullScreen',
104
- false,
105
- );
106
- expect(stubNode.hasAttribute('allowFullScreen')).toBe(false);
86
+ var container = document.createElement('div');
87
+ ReactDOM.render(<div allowFullScreen={false} />, container);
88
+ expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false);
89
});
90
91
it('should remove when setting custom attr to null', () => {
110
- DOMPropertyOperations.setValueForProperty(stubNode, 'data-foo', 'bar');
111
- expect(stubNode.hasAttribute('data-foo')).toBe(true);
112
- DOMPropertyOperations.setValueForProperty(stubNode, 'data-foo', null);
113
- expect(stubNode.hasAttribute('data-foo')).toBe(false);
114
- });
115
-
116
- it('should use mutation method where applicable', () => {
117
- var foobarSetter = jest.fn();
118
- // inject foobar DOM property
119
- DOMProperty.injection.injectDOMPropertyConfig({
120
- Properties: {foobar: null},
121
- DOMMutationMethods: {
122
- foobar: foobarSetter,
123
- },
124
- });
125
-
126
- DOMPropertyOperations.setValueForProperty(
127
- stubNode,
128
- 'foobar',
129
- 'cows say moo',
130
- );
131
-
132
- expect(foobarSetter.mock.calls.length).toBe(1);
133
- expect(foobarSetter.mock.calls[0][0]).toBe(stubNode);
134
- expect(foobarSetter.mock.calls[0][1]).toBe('cows say moo');
92
+ var container = document.createElement('div');
93
+ ReactDOM.render(<div data-foo="bar" />, container);
94
+ expect(container.firstChild.hasAttribute('data-foo')).toBe(true);
95
+ ReactDOM.render(<div data-foo={null} />, container);
96
+ expect(container.firstChild.hasAttribute('data-foo')).toBe(false);
97
});
98
99
it('should set className to empty string instead of null', () => {
138
- DOMPropertyOperations.setValueForProperty(
139
- stubNode,
140
- 'className',
141
- 'selected',
142
- );
143
- expect(stubNode.className).toBe('selected');
144
-
145
- DOMPropertyOperations.setValueForProperty(stubNode, 'className', null);
100
+ var container = document.createElement('div');
101
+ ReactDOM.render(<div className="selected" />, container);
102
+ expect(container.firstChild.className).toBe('selected');
103
+ ReactDOM.render(<div className={null} />, container);
104
// className should be '', not 'null' or null (which becomes 'null' in
105
// some browsers)
148
- expect(stubNode.className).toBe('');
149
- expect(stubNode.getAttribute('class')).toBe(null);
106
+ expect(container.firstChild.className).toBe('');
107
+ expect(container.firstChild.getAttribute('class')).toBe(null);
108
});
109
110
it('should remove property properly for boolean properties', () => {
153
- DOMPropertyOperations.setValueForProperty(stubNode, 'hidden', true);
154
- expect(stubNode.hasAttribute('hidden')).toBe(true);
155
-
156
- DOMPropertyOperations.setValueForProperty(stubNode, 'hidden', false);
157
- expect(stubNode.hasAttribute('hidden')).toBe(false);
158
- });
159
-
160
- it('should remove property properly even with different name', () => {
161
- // Suppose 'foobar' is a property that corresponds to the underlying
162
- // 'className' property:
163
- DOMProperty.injection.injectDOMPropertyConfig({
164
- Properties: {foobar: DOMProperty.injection.MUST_USE_PROPERTY},
165
- DOMPropertyNames: {
166
- foobar: 'className',
167
- },
168
- DOMAttributeNames: {
169
- foobar: 'class',
170
- },
171
- });
172
-
173
- DOMPropertyOperations.setValueForProperty(stubNode, 'foobar', 'selected');
174
- expect(stubNode.className).toBe('selected');
175
-
176
- DOMPropertyOperations.setValueForProperty(stubNode, 'foobar', null);
177
- // className should be '', not 'null' or null (which becomes 'null' in
178
- // some browsers)
179
- expect(stubNode.className).toBe('');
111
+ var container = document.createElement('div');
112
+ ReactDOM.render(<div hidden={true} />, container);
113
+ expect(container.firstChild.hasAttribute('hidden')).toBe(true);
114
+ ReactDOM.render(<div hidden={false} />, container);
115
+ expect(container.firstChild.hasAttribute('hidden')).toBe(false);
116
});
117
});
118
119
describe('value mutation method', function() {
120
it('should update an empty attribute to zero', function() {
185
- var stubNode = document.createElement('input');
186
- var stubInstance = {_debugID: 1};
187
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
188
-
189
- stubNode.setAttribute('type', 'radio');
190
-
191
- DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
192
- spyOn(stubNode, 'setAttribute');
193
- DOMPropertyOperations.setValueForProperty(stubNode, 'value', 0);
194
-
195
- expect(stubNode.setAttribute.calls.count()).toBe(1);
121
+ var container = document.createElement('div');
122
+ ReactDOM.render(
123
+ <input type="radio" value="" onChange={function() {}} />,
124
+ container,
125
+ );
126
+ spyOn(container.firstChild, 'setAttribute');
127
+ ReactDOM.render(
128
+ <input type="radio" value={0} onChange={function() {}} />,
129
+ container,
130
+ );
131
+ expect(container.firstChild.setAttribute.calls.count()).toBe(1);
132
});
133
134
it('should always assign the value attribute for non-inputs', function() {
199
- var stubNode = document.createElement('progress');
200
- var stubInstance = {_debugID: 1};
201
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
202
-
203
- spyOn(stubNode, 'setAttribute');
204
-
205
- DOMPropertyOperations.setValueForProperty(stubNode, 'value', 30);
206
- DOMPropertyOperations.setValueForProperty(stubNode, 'value', '30');
207
-
208
- expect(stubNode.setAttribute.calls.count()).toBe(2);
135
+ var container = document.createElement('div');
136
+ ReactDOM.render(<progress />, container);
137
+ spyOn(container.firstChild, 'setAttribute');
138
+ ReactDOM.render(<progress value={30} />, container);
139
+ ReactDOM.render(<progress value="30" />, container);
140
+ expect(container.firstChild.setAttribute.calls.count()).toBe(2);
141
});
142
});
143
144
describe('deleteValueForProperty', () => {
213
- var stubNode;
214
- var stubInstance;
215
-
216
- beforeEach(() => {
217
- stubNode = document.createElement('div');
218
- stubInstance = {_debugID: 1};
219
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
220
- });
221
-
145
it('should remove attributes for normal properties', () => {
223
- DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'foo');
224
- expect(stubNode.getAttribute('title')).toBe('foo');
225
- expect(stubNode.title).toBe('foo');
226
-
227
- DOMPropertyOperations.deleteValueForProperty(stubNode, 'title');
228
- expect(stubNode.getAttribute('title')).toBe(null);
229
- // JSDOM does not behave correctly for attributes/properties
230
- //expect(stubNode.title).toBe('');
146
+ var container = document.createElement('div');
147
+ ReactDOM.render(<div title="foo" />, container);
148
+ expect(container.firstChild.getAttribute('title')).toBe('foo');
149
+ ReactDOM.render(<div />, container);
150
+ expect(container.firstChild.getAttribute('title')).toBe(null);
151
});
152
153
it('should not remove attributes for special properties', () => {
234
- stubNode = document.createElement('input');
235
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
236
-
237
- stubNode.setAttribute('value', 'foo');
238
-
239
- DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
240
- // JSDOM does not behave correctly for attributes/properties
241
- //expect(stubNode.getAttribute('value')).toBe('foo');
242
- expect(stubNode.value).toBe('');
243
- });
244
-
245
- it('should not leave all options selected when deleting multiple', () => {
246
- stubNode = document.createElement('select');
247
- ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
248
-
249
- stubNode.multiple = true;
250
- stubNode.appendChild(document.createElement('option'));
251
- stubNode.appendChild(document.createElement('option'));
252
- stubNode.options[0].selected = true;
253
- stubNode.options[1].selected = true;
254
-
255
- DOMPropertyOperations.deleteValueForProperty(stubNode, 'multiple');
256
- expect(stubNode.getAttribute('multiple')).toBe(null);
257
- expect(stubNode.multiple).toBe(false);
258
-
259
- expect(stubNode.options[0].selected && stubNode.options[1].selected).toBe(
260
- false,
154
+ var container = document.createElement('div');
155
+ spyOn(console, 'error');
156
+ ReactDOM.render(
157
+ <input type="text" value="foo" onChange={function() {}} />,
158
+ container,
159
+ );
160
+ expect(container.firstChild.getAttribute('value')).toBe('foo');
161
+ expect(container.firstChild.value).toBe('foo');
162
+ ReactDOM.render(
163
+ <input type="text" onChange={function() {}} />,
164
+ container,
165
+ );
166
+ expect(container.firstChild.getAttribute('value')).toBe('foo');
167
+ expect(container.firstChild.value).toBe('foo');
168
+ expect(console.error.calls.count()).toBe(1);
169
+ expect(console.error.calls.argsFor(0)[0]).toContain(
170
+ 'A component is changing a controlled input of type text to be uncontrolled',
171
);
172
});
173
});