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() {