@samitouri / QOS-React / commits / 4e4653da9b

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