@samitouri / QOS-React / commits / 77a137adf9

Use removeAttribute to forcefully remove properties from the DOM, drop MUST_USE_ATTRIBUTE and manage all regular properties as attributes instead

Andreas Svensson committed May 10, 2014 at 22:16 UTC 77a137adf917c0ab87a6c9b6fec58938b14c38ce
6 files changed +278 -191
src/renderers/dom/shared/DOMProperty.js
+6 -38
@@ -22,13 +22,12 @@ var DOMPropertyInjection = {
22 * Mapping from normalized, camelcased property names to a configuration that
23 * specifies how the associated DOM property should be accessed or rendered.
24 */
25 - MUST_USE_ATTRIBUTE: 0x1,
26 - MUST_USE_PROPERTY: 0x2,
27 - HAS_SIDE_EFFECTS: 0x4,
28 - HAS_BOOLEAN_VALUE: 0x8,
29 - HAS_NUMERIC_VALUE: 0x10,
30 - HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10,
31 - HAS_OVERLOADED_BOOLEAN_VALUE: 0x40,
25 + MUST_USE_PROPERTY: 0x1,
26 + HAS_SIDE_EFFECTS: 0x2,
27 + HAS_BOOLEAN_VALUE: 0x4,
28 + HAS_NUMERIC_VALUE: 0x8,
29 + HAS_POSITIVE_NUMERIC_VALUE: 0x10 | 0x8,
30 + HAS_OVERLOADED_BOOLEAN_VALUE: 0x20,
31
32 /**
33 * Inject some specialized knowledge about the DOM. This takes a config object
@@ -91,7 +90,6 @@ var DOMPropertyInjection = {
90 propertyName: propName,
91 mutationMethod: null,
92
94 - mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE),
93 mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
94 hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS),
95 hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
@@ -102,11 +100,6 @@ var DOMPropertyInjection = {
100 checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
101 };
102
105 - invariant(
106 - !propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty,
107 - 'DOMProperty: Cannot require using both attribute and property: %s',
108 - propName
109 - );
103 invariant(
104 propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects,
105 'DOMProperty: Properties that have side effects must use property: %s',
@@ -148,7 +141,6 @@ var DOMPropertyInjection = {
141 }
142 },
143 };
151 -var defaultValueCache = {};
144
145 var ATTRIBUTE_NAME_START_CHAR = ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
146
@@ -186,9 +178,6 @@ var DOMProperty = {
178 * mutationMethod:
179 * If non-null, used instead of the property or `setAttribute()` after
180 * initial render.
189 - * mustUseAttribute:
190 - * Whether the property must be accessed and mutated using `*Attribute()`.
191 - * (This includes anything that fails `<propName> in <element>`.)
181 * mustUseProperty:
182 * Whether the property must be accessed and mutated as an object property.
183 * hasSideEffects:
@@ -237,27 +226,6 @@ var DOMProperty = {
226 return false;
227 },
228
240 - /**
241 - * Returns the default property value for a DOM property (i.e., not an
242 - * attribute). Most default values are '' or false, but not all. Worse yet,
243 - * some (in particular, `type`) vary depending on the type of element.
244 - *
245 - * TODO: Is it better to grab all the possible properties when creating an
246 - * element to avoid having to create the same element twice?
247 - */
248 - getDefaultValueForProperty: function(nodeName, prop) {
249 - var nodeDefaults = defaultValueCache[nodeName];
250 - var testElement;
251 - if (!nodeDefaults) {
252 - defaultValueCache[nodeName] = nodeDefaults = {};
253 - }
254 - if (!(prop in nodeDefaults)) {
255 - testElement = document.createElement(nodeName);
256 - nodeDefaults[prop] = testElement[prop];
257 - }
258 - return nodeDefaults[prop];
259 - },
260 -
229 injection: DOMPropertyInjection,
230 };
231
src/renderers/dom/shared/DOMPropertyOperations.js
+22 -21
@@ -168,7 +168,17 @@ var DOMPropertyOperations = {
168 mutationMethod(node, value);
169 } else if (shouldIgnoreValue(propertyInfo, value)) {
170 this.deleteValueForProperty(node, name);
171 - } else if (propertyInfo.mustUseAttribute) {
171 + } else if (propertyInfo.mustUseProperty) {
172 + var propName = propertyInfo.propertyName;
173 + // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
174 + // property type before comparing; only `value` does and is string.
175 + if (!propertyInfo.hasSideEffects ||
176 + ('' + node[propName]) !== ('' + value)) {
177 + // Contrary to `setAttribute`, object properties are properly
178 + // `toString`ed by IE8/9.
179 + node[propName] = value;
180 + }
181 + } else {
182 var attributeName = propertyInfo.attributeName;
183 var namespace = propertyInfo.attributeNamespace;
184 // `setAttribute` with objects becomes only `[object]` in IE8/9,
@@ -181,16 +191,6 @@ var DOMPropertyOperations = {
191 } else {
192 node.setAttribute(attributeName, '' + value);
193 }
184 - } else {
185 - var propName = propertyInfo.propertyName;
186 - // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
187 - // property type before comparing; only `value` does and is string.
188 - if (!propertyInfo.hasSideEffects ||
189 - ('' + node[propName]) !== ('' + value)) {
190 - // Contrary to `setAttribute`, object properties are properly
191 - // `toString`ed by IE8/9.
192 - node[propName] = value;
193 - }
194 }
195 } else if (DOMProperty.isCustomAttribute(name)) {
196 DOMPropertyOperations.setValueForAttribute(node, name, value);
@@ -236,18 +236,19 @@ var DOMPropertyOperations = {
236 var mutationMethod = propertyInfo.mutationMethod;
237 if (mutationMethod) {
238 mutationMethod(node, undefined);
239 - } else if (propertyInfo.mustUseAttribute) {
240 - node.removeAttribute(propertyInfo.attributeName);
241 - } else {
239 + } else if (propertyInfo.mustUseProperty) {
240 var propName = propertyInfo.propertyName;
243 - var defaultValue = DOMProperty.getDefaultValueForProperty(
244 - node.nodeName,
245 - propName
246 - );
247 - if (!propertyInfo.hasSideEffects ||
248 - ('' + node[propName]) !== defaultValue) {
249 - node[propName] = defaultValue;
241 + if (propertyInfo.hasBooleanValue) {
242 + // No HAS_SIDE_EFFECTS logic here, only `value` has it and is string.
243 + node[propName] = false;
244 + } else {
245 + if (!propertyInfo.hasSideEffects ||
246 + ('' + node[propName]) !== '') {
247 + node[propName] = '';
248 + }
249 }
250 + } else {
251 + node.removeAttribute(propertyInfo.attributeName);
252 }
253 } else if (DOMProperty.isCustomAttribute(name)) {
254 node.removeAttribute(name);
src/renderers/dom/shared/HTMLDOMPropertyConfig.js
+76 -103
@@ -12,9 +12,7 @@
12 'use strict';
13
14 var DOMProperty = require('DOMProperty');
15 -var ExecutionEnvironment = require('ExecutionEnvironment');
15
17 -var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
16 var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY;
17 var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE;
18 var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS;
@@ -24,19 +22,6 @@ var HAS_POSITIVE_NUMERIC_VALUE =
22 var HAS_OVERLOADED_BOOLEAN_VALUE =
23 DOMProperty.injection.HAS_OVERLOADED_BOOLEAN_VALUE;
24
27 -var hasSVG;
28 -if (ExecutionEnvironment.canUseDOM) {
29 - var implementation = document.implementation;
30 - hasSVG = (
31 - implementation &&
32 - implementation.hasFeature &&
33 - implementation.hasFeature(
34 - 'http://www.w3.org/TR/SVG11/feature#BasicStructure',
35 - '1.1'
36 - )
37 - );
38 -}
39 -
25 var HTMLDOMPropertyConfig = {
26 isCustomAttribute: RegExp.prototype.test.bind(
27 new RegExp('^(data|aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$')
@@ -49,86 +34,83 @@ var HTMLDOMPropertyConfig = {
34 acceptCharset: null,
35 accessKey: null,
36 action: null,
52 - allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
53 - allowTransparency: MUST_USE_ATTRIBUTE,
37 + allowFullScreen: HAS_BOOLEAN_VALUE,
38 + allowTransparency: null,
39 alt: null,
40 async: HAS_BOOLEAN_VALUE,
41 autoComplete: null,
42 // autoFocus is polyfilled/normalized by AutoFocusUtils
43 // autoFocus: HAS_BOOLEAN_VALUE,
44 autoPlay: HAS_BOOLEAN_VALUE,
60 - capture: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
61 - cellPadding: MUST_USE_ATTRIBUTE,
62 - cellSpacing: MUST_USE_ATTRIBUTE,
63 - challenge: MUST_USE_ATTRIBUTE,
64 - charSet: MUST_USE_ATTRIBUTE,
45 + capture: HAS_BOOLEAN_VALUE,
46 + cellPadding: null,
47 + cellSpacing: null,
48 + charSet: null,
49 + challenge: null,
50 checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
66 - classID: MUST_USE_ATTRIBUTE,
67 - // To set className on SVG elements, it's necessary to use .setAttribute;
68 - // this works on HTML elements too in all browsers except IE8. Conveniently,
69 - // IE8 doesn't support SVG and so we can simply use the attribute in
70 - // browsers that support SVG and the property in browsers that don't,
71 - // regardless of whether the element is HTML or SVG.
72 - className: hasSVG ? MUST_USE_ATTRIBUTE : MUST_USE_PROPERTY,
73 - cols: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
74 - colSpan: MUST_USE_ATTRIBUTE,
51 + classID: null,
52 + className: null,
53 + cols: HAS_POSITIVE_NUMERIC_VALUE,
54 + colSpan: null,
55 content: null,
76 - contentEditable: MUST_USE_ATTRIBUTE,
77 - contextMenu: MUST_USE_ATTRIBUTE,
78 - controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
56 + contentEditable: null,
57 + contextMenu: null,
58 + controls: HAS_BOOLEAN_VALUE,
59 coords: null,
60 crossOrigin: null,
61 data: null, // For `<object />` acts as `src`.
82 - dateTime: MUST_USE_ATTRIBUTE,
62 + dateTime: null,
63 default: HAS_BOOLEAN_VALUE,
64 defer: HAS_BOOLEAN_VALUE,
85 - dir: MUST_USE_ATTRIBUTE,
86 - disabled: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
65 + dir: null,
66 + disabled: HAS_BOOLEAN_VALUE,
67 download: HAS_OVERLOADED_BOOLEAN_VALUE,
68 draggable: null,
89 - encType: MUST_USE_ATTRIBUTE,
90 - form: MUST_USE_ATTRIBUTE,
91 - formAction: MUST_USE_ATTRIBUTE,
92 - formEncType: MUST_USE_ATTRIBUTE,
93 - formMethod: MUST_USE_ATTRIBUTE,
69 + encType: null,
70 + form: null,
71 + formAction: null,
72 + formEncType: null,
73 + formMethod: null,
74 formNoValidate: HAS_BOOLEAN_VALUE,
95 - formTarget: MUST_USE_ATTRIBUTE,
96 - frameBorder: MUST_USE_ATTRIBUTE,
75 + formTarget: null,
76 + frameBorder: null,
77 headers: null,
98 - height: MUST_USE_ATTRIBUTE,
99 - hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
78 + height: null,
79 + hidden: HAS_BOOLEAN_VALUE,
80 high: null,
81 href: null,
82 hrefLang: null,
83 htmlFor: null,
84 httpEquiv: null,
85 icon: null,
106 - id: MUST_USE_PROPERTY,
107 - inputMode: MUST_USE_ATTRIBUTE,
86 + id: null,
87 + inputMode: null,
88 integrity: null,
109 - is: MUST_USE_ATTRIBUTE,
110 - keyParams: MUST_USE_ATTRIBUTE,
111 - keyType: MUST_USE_ATTRIBUTE,
89 + is: null,
90 + keyParams: null,
91 + keyType: null,
92 kind: null,
93 label: null,
94 lang: null,
115 - list: MUST_USE_ATTRIBUTE,
116 - loop: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
95 + list: null,
96 + loop: HAS_BOOLEAN_VALUE,
97 low: null,
118 - manifest: MUST_USE_ATTRIBUTE,
119 - marginHeight: MUST_USE_ATTRIBUTE,
120 - marginWidth: MUST_USE_ATTRIBUTE,
98 + manifest: null,
99 + marginHeight: null,
100 + marginWidth: null,
101 max: null,
122 - maxLength: MUST_USE_ATTRIBUTE,
123 - media: MUST_USE_ATTRIBUTE,
102 + maxLength: null,
103 + media: null,
104 mediaGroup: null,
125 - method: MUST_USE_ATTRIBUTE,
105 + method: null,
106 min: null,
127 - minLength: MUST_USE_ATTRIBUTE,
107 + minLength: null,
108 + // Caution; `option.selected` is not updated if `select.multiple` is
109 + // disabled with `removeAttribute`.
110 multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
111 muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
112 name: null,
131 - nonce: MUST_USE_ATTRIBUTE,
113 + nonce: null,
114 noValidate: HAS_BOOLEAN_VALUE,
115 open: HAS_BOOLEAN_VALUE,
116 optimum: null,
@@ -137,29 +119,29 @@ var HTMLDOMPropertyConfig = {
119 poster: null,
120 preload: null,
121 radioGroup: null,
140 - readOnly: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
122 + readOnly: HAS_BOOLEAN_VALUE,
123 rel: null,
124 required: HAS_BOOLEAN_VALUE,
125 reversed: HAS_BOOLEAN_VALUE,
144 - role: MUST_USE_ATTRIBUTE,
145 - rows: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
146 - rowSpan: MUST_USE_ATTRIBUTE | HAS_NUMERIC_VALUE,
126 + role: null,
127 + rows: HAS_POSITIVE_NUMERIC_VALUE,
128 + rowSpan: HAS_NUMERIC_VALUE,
129 sandbox: null,
130 scope: null,
131 scoped: HAS_BOOLEAN_VALUE,
150 - scrolling: MUST_USE_ATTRIBUTE,
151 - seamless: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
132 + scrolling: null,
133 + seamless: HAS_BOOLEAN_VALUE,
134 selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
135 shape: null,
154 - size: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
155 - sizes: MUST_USE_ATTRIBUTE,
156 - span: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
136 + size: HAS_POSITIVE_NUMERIC_VALUE,
137 + sizes: null,
138 + span: HAS_POSITIVE_NUMERIC_VALUE,
139 spellCheck: null,
140 src: null,
159 - srcDoc: MUST_USE_PROPERTY,
141 + srcDoc: null,
142 srcLang: null,
161 - srcSet: MUST_USE_ATTRIBUTE,
162 - start: MUST_USE_ATTRIBUTE | HAS_NUMERIC_VALUE,
143 + srcSet: null,
144 + start: HAS_NUMERIC_VALUE,
145 step: null,
146 style: null,
147 summary: null,
@@ -167,55 +149,55 @@ var HTMLDOMPropertyConfig = {
149 target: null,
150 title: null,
151 // Setting .type throws on non-<input> tags
170 - type: MUST_USE_ATTRIBUTE,
152 + type: null,
153 useMap: null,
154 value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
173 - width: MUST_USE_ATTRIBUTE,
174 - wmode: MUST_USE_ATTRIBUTE,
175 - wrap: MUST_USE_ATTRIBUTE,
155 + width: null,
156 + wmode: null,
157 + wrap: null,
158
159 /**
160 * RDFa Properties
161 */
180 - about: MUST_USE_ATTRIBUTE,
181 - datatype: MUST_USE_ATTRIBUTE,
182 - inlist: MUST_USE_ATTRIBUTE,
183 - prefix: MUST_USE_ATTRIBUTE,
162 + about: null,
163 + datatype: null,
164 + inlist: null,
165 + prefix: null,
166 // property is also supported for OpenGraph in meta tags.
185 - property: MUST_USE_ATTRIBUTE,
186 - resource: MUST_USE_ATTRIBUTE,
187 - typeof: MUST_USE_ATTRIBUTE,
188 - vocab: MUST_USE_ATTRIBUTE,
167 + property: null,
168 + resource: null,
169 + typeof: null,
170 + vocab: null,
171
172 /**
173 * Non-standard Properties
174 */
175 // autoCapitalize and autoCorrect are supported in Mobile Safari for
176 // keyboard hints.
195 - autoCapitalize: MUST_USE_ATTRIBUTE,
196 - autoCorrect: MUST_USE_ATTRIBUTE,
177 + autoCapitalize: null,
178 + autoCorrect: null,
179 // autoSave allows WebKit/Blink to persist values of input fields on page reloads
180 autoSave: null,
181 // color is for Safari mask-icon link
182 color: null,
183 // itemProp, itemScope, itemType are for
184 // Microdata support. See http://schema.org/docs/gs.html
203 - itemProp: MUST_USE_ATTRIBUTE,
204 - itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
205 - itemType: MUST_USE_ATTRIBUTE,
185 + itemProp: null,
186 + itemScope: HAS_BOOLEAN_VALUE,
187 + itemType: null,
188 // itemID and itemRef are for Microdata support as well but
189 // only specified in the WHATWG spec document. See
190 // https://html.spec.whatwg.org/multipage/microdata.html#microdata-dom-api
209 - itemID: MUST_USE_ATTRIBUTE,
210 - itemRef: MUST_USE_ATTRIBUTE,
191 + itemID: null,
192 + itemRef: null,
193 // results show looking glass icon and recent searches on input
194 // search fields in WebKit/Blink
195 results: null,
196 // IE-only attribute that specifies security restrictions on an iframe
197 // as an alternative to the sandbox attribute on IE<10
216 - security: MUST_USE_ATTRIBUTE,
198 + security: null,
199 // IE-only attribute that controls focus behavior
218 - unselectable: MUST_USE_ATTRIBUTE,
200 + unselectable: null,
201 },
202 DOMAttributeNames: {
203 acceptCharset: 'accept-charset',
@@ -224,15 +206,6 @@ var HTMLDOMPropertyConfig = {
206 httpEquiv: 'http-equiv',
207 },
208 DOMPropertyNames: {
227 - autoComplete: 'autocomplete',
228 - autoFocus: 'autofocus',
229 - autoPlay: 'autoplay',
230 - autoSave: 'autosave',
231 - hrefLang: 'hreflang',
232 - radioGroup: 'radiogroup',
233 - spellCheck: 'spellcheck',
234 - srcDoc: 'srcdoc',
235 - srcSet: 'srcset',
209 },
210 };
211
src/renderers/dom/shared/SVGDOMPropertyConfig.js
+24 -28
@@ -11,10 +11,6 @@
11
12 'use strict';
13
14 -var DOMProperty = require('DOMProperty');
15 -
16 -var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
17 -
14 var NS = {
15 xlink: 'http://www.w3.org/1999/xlink',
16 xml: 'http://www.w3.org/XML/1998/namespace',
@@ -22,30 +18,30 @@ var NS = {
18
19 var SVGDOMPropertyConfig = {
20 Properties: {
25 - clipPath: MUST_USE_ATTRIBUTE,
26 - fillOpacity: MUST_USE_ATTRIBUTE,
27 - fontFamily: MUST_USE_ATTRIBUTE,
28 - fontSize: MUST_USE_ATTRIBUTE,
29 - markerEnd: MUST_USE_ATTRIBUTE,
30 - markerMid: MUST_USE_ATTRIBUTE,
31 - markerStart: MUST_USE_ATTRIBUTE,
32 - stopColor: MUST_USE_ATTRIBUTE,
33 - stopOpacity: MUST_USE_ATTRIBUTE,
34 - strokeDasharray: MUST_USE_ATTRIBUTE,
35 - strokeLinecap: MUST_USE_ATTRIBUTE,
36 - strokeOpacity: MUST_USE_ATTRIBUTE,
37 - strokeWidth: MUST_USE_ATTRIBUTE,
38 - textAnchor: MUST_USE_ATTRIBUTE,
39 - xlinkActuate: MUST_USE_ATTRIBUTE,
40 - xlinkArcrole: MUST_USE_ATTRIBUTE,
41 - xlinkHref: MUST_USE_ATTRIBUTE,
42 - xlinkRole: MUST_USE_ATTRIBUTE,
43 - xlinkShow: MUST_USE_ATTRIBUTE,
44 - xlinkTitle: MUST_USE_ATTRIBUTE,
45 - xlinkType: MUST_USE_ATTRIBUTE,
46 - xmlBase: MUST_USE_ATTRIBUTE,
47 - xmlLang: MUST_USE_ATTRIBUTE,
48 - xmlSpace: MUST_USE_ATTRIBUTE,
21 + clipPath: null,
22 + fillOpacity: null,
23 + fontFamily: null,
24 + fontSize: null,
25 + markerEnd: null,
26 + markerMid: null,
27 + markerStart: null,
28 + stopColor: null,
29 + stopOpacity: null,
30 + strokeDasharray: null,
31 + strokeLinecap: null,
32 + strokeOpacity: null,
33 + strokeWidth: null,
34 + textAnchor: null,
35 + xlinkActuate: null,
36 + xlinkArcrole: null,
37 + xlinkHref: null,
38 + xlinkRole: null,
39 + xlinkShow: null,
40 + xlinkTitle: null,
41 + xlinkType: null,
42 + xmlBase: null,
43 + xmlLang: null,
44 + xmlSpace: null,
45 },
46 DOMAttributeNamespaces: {
47 xlinkActuate: NS.xlink,
src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js
+67
@@ -282,6 +282,23 @@ describe('DOMPropertyOperations', function() {
282 // className should be '', not 'null' or null (which becomes 'null' in
283 // some browsers)
284 expect(stubNode.className).toBe('');
285 + expect(stubNode.getAttribute('class')).toBe(null);
286 + });
287 +
288 + it('should remove property properly for boolean properties', function() {
289 + DOMPropertyOperations.setValueForProperty(
290 + stubNode,
291 + 'hidden',
292 + true
293 + );
294 + expect(stubNode.hasAttribute('hidden')).toBe(true);
295 +
296 + DOMPropertyOperations.setValueForProperty(
297 + stubNode,
298 + 'hidden',
299 + false
300 + );
301 + expect(stubNode.hasAttribute('hidden')).toBe(false);
302 });
303
304 it('should remove property properly even with different name', function() {
@@ -292,6 +309,9 @@ describe('DOMPropertyOperations', function() {
309 DOMPropertyNames: {
310 foobar: 'className',
311 },
312 + DOMAttributeNames: {
313 + foobar: 'class',
314 + },
315 });
316
317 DOMPropertyOperations.setValueForProperty(
@@ -313,6 +333,53 @@ describe('DOMPropertyOperations', function() {
333
334 });
335
336 + describe('deleteValueForProperty', function() {
337 + var stubNode;
338 +
339 + beforeEach(function() {
340 + stubNode = document.createElement('div');
341 + });
342 +
343 + it('should remove attributes for normal properties', function() {
344 + DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'foo');
345 + expect(stubNode.getAttribute('title')).toBe('foo');
346 + expect(stubNode.title).toBe('foo');
347 +
348 + DOMPropertyOperations.deleteValueForProperty(stubNode, 'title');
349 + expect(stubNode.getAttribute('title')).toBe(null);
350 + // JSDOM does not behave correctly for attributes/properties
351 + //expect(stubNode.title).toBe('');
352 + });
353 +
354 + it('should not remove attributes for special properties', function() {
355 + stubNode = document.createElement('input');
356 + stubNode.setAttribute('value', 'foo');
357 +
358 + DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
359 + // JSDOM does not behave correctly for attributes/properties
360 + //expect(stubNode.getAttribute('value')).toBe('foo');
361 + expect(stubNode.value).toBe('');
362 + });
363 +
364 + it('should not leave all options selected when deleting multiple', function() {
365 + stubNode = document.createElement('select');
366 + stubNode.multiple = true;
367 + stubNode.appendChild(document.createElement('option'));
368 + stubNode.appendChild(document.createElement('option'));
369 + stubNode.options[0].selected = true;
370 + stubNode.options[1].selected = true;
371 +
372 + DOMPropertyOperations.deleteValueForProperty(stubNode, 'multiple');
373 + expect(stubNode.getAttribute('multiple')).toBe(null);
374 + expect(stubNode.multiple).toBe(false);
375 +
376 + expect(
377 + stubNode.options[0].selected &&
378 + stubNode.options[1].selected
379 + ).toBe(false);
380 + });
381 + });
382 +
383 describe('injectDOMPropertyConfig', function() {
384 it('should support custom attributes', function() {
385 // foobar does not exist yet
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+83 -1
@@ -573,7 +573,45 @@ describe('ReactDOMComponent', function() {
573 expect(container.textContent).toEqual('bonjour');
574 });
575
576 - it('should not incur unnecessary DOM mutations', function() {
576 + it('should not incur unnecessary DOM mutations for attributes', function() {
577 + var container = document.createElement('div');
578 + ReactDOM.render(<div id="" />, container);
579 +
580 + var node = container.firstChild;
581 + var nodeSetAttribute = node.setAttribute;
582 + node.setAttribute = jest.genMockFn();
583 + node.setAttribute.mockImpl(nodeSetAttribute);
584 +
585 + var nodeRemoveAttribute = node.removeAttribute;
586 + node.removeAttribute = jest.genMockFn();
587 + node.removeAttribute.mockImpl(nodeRemoveAttribute);
588 +
589 + ReactDOM.render(<div id="" />, container);
590 + expect(node.setAttribute.mock.calls.length).toBe(0);
591 + expect(node.removeAttribute.mock.calls.length).toBe(0);
592 +
593 + ReactDOM.render(<div id="foo" />, container);
594 + expect(node.setAttribute.mock.calls.length).toBe(1);
595 + expect(node.removeAttribute.mock.calls.length).toBe(0);
596 +
597 + ReactDOM.render(<div id="foo" />, container);
598 + expect(node.setAttribute.mock.calls.length).toBe(1);
599 + expect(node.removeAttribute.mock.calls.length).toBe(0);
600 +
601 + ReactDOM.render(<div />, container);
602 + expect(node.setAttribute.mock.calls.length).toBe(1);
603 + expect(node.removeAttribute.mock.calls.length).toBe(1);
604 +
605 + ReactDOM.render(<div id="" />, container);
606 + expect(node.setAttribute.mock.calls.length).toBe(2);
607 + expect(node.removeAttribute.mock.calls.length).toBe(1);
608 +
609 + ReactDOM.render(<div />, container);
610 + expect(node.setAttribute.mock.calls.length).toBe(2);
611 + expect(node.removeAttribute.mock.calls.length).toBe(2);
612 + });
613 +
614 + it('should not incur unnecessary DOM mutations for string properties', function() {
615 var container = document.createElement('div');
616 ReactDOM.render(<div value="" />, container);
617
@@ -592,8 +630,52 @@ describe('ReactDOMComponent', function() {
630 ReactDOM.render(<div value="" />, container);
631 expect(nodeValueSetter.mock.calls.length).toBe(0);
632
633 + ReactDOM.render(<div value="foo" />, container);
634 + expect(nodeValueSetter.mock.calls.length).toBe(1);
635 +
636 + ReactDOM.render(<div value="foo" />, container);
637 + expect(nodeValueSetter.mock.calls.length).toBe(1);
638 +
639 + ReactDOM.render(<div />, container);
640 + expect(nodeValueSetter.mock.calls.length).toBe(2);
641 +
642 + ReactDOM.render(<div value={null} />, container);
643 + expect(nodeValueSetter.mock.calls.length).toBe(2);
644 +
645 + ReactDOM.render(<div value="" />, container);
646 + expect(nodeValueSetter.mock.calls.length).toBe(2);
647 +
648 + ReactDOM.render(<div />, container);
649 + expect(nodeValueSetter.mock.calls.length).toBe(2);
650 + });
651 +
652 + it('should not incur unnecessary DOM mutations for boolean properties', function() {
653 + var container = document.createElement('div');
654 + ReactDOM.render(<div checked={true} />, container);
655 +
656 + var node = container.firstChild;
657 + var nodeValue = true;
658 + var nodeValueSetter = jest.genMockFn();
659 + Object.defineProperty(node, 'checked', {
660 + get: function() {
661 + return nodeValue;
662 + },
663 + set: nodeValueSetter.mockImplementation(function(newValue) {
664 + nodeValue = newValue;
665 + }),
666 + });
667 +
668 + ReactDOM.render(<div checked={true} />, container);
669 + expect(nodeValueSetter.mock.calls.length).toBe(0);
670 +
671 ReactDOM.render(<div />, container);
672 expect(nodeValueSetter.mock.calls.length).toBe(1);
673 +
674 + ReactDOM.render(<div checked={false} />, container);
675 + expect(nodeValueSetter.mock.calls.length).toBe(2); // should be 1
676 +
677 + ReactDOM.render(<div checked={true} />, container);
678 + expect(nodeValueSetter.mock.calls.length).toBe(3);
679 });
680
681 it('should ignore attribute whitelist for elements with the "is: attribute', function() {