mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #1510 from syranide/propattr
Use removeAttribute to forcefully remove properties from the DOM
This commit is contained in:
@@ -22,13 +22,12 @@ var DOMPropertyInjection = {
|
||||
* Mapping from normalized, camelcased property names to a configuration that
|
||||
* specifies how the associated DOM property should be accessed or rendered.
|
||||
*/
|
||||
MUST_USE_ATTRIBUTE: 0x1,
|
||||
MUST_USE_PROPERTY: 0x2,
|
||||
HAS_SIDE_EFFECTS: 0x4,
|
||||
HAS_BOOLEAN_VALUE: 0x8,
|
||||
HAS_NUMERIC_VALUE: 0x10,
|
||||
HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10,
|
||||
HAS_OVERLOADED_BOOLEAN_VALUE: 0x40,
|
||||
MUST_USE_PROPERTY: 0x1,
|
||||
HAS_SIDE_EFFECTS: 0x2,
|
||||
HAS_BOOLEAN_VALUE: 0x4,
|
||||
HAS_NUMERIC_VALUE: 0x8,
|
||||
HAS_POSITIVE_NUMERIC_VALUE: 0x10 | 0x8,
|
||||
HAS_OVERLOADED_BOOLEAN_VALUE: 0x20,
|
||||
|
||||
/**
|
||||
* Inject some specialized knowledge about the DOM. This takes a config object
|
||||
@@ -91,7 +90,6 @@ var DOMPropertyInjection = {
|
||||
propertyName: propName,
|
||||
mutationMethod: null,
|
||||
|
||||
mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE),
|
||||
mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
|
||||
hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS),
|
||||
hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
|
||||
@@ -102,11 +100,6 @@ var DOMPropertyInjection = {
|
||||
checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
|
||||
};
|
||||
|
||||
invariant(
|
||||
!propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty,
|
||||
'DOMProperty: Cannot require using both attribute and property: %s',
|
||||
propName
|
||||
);
|
||||
invariant(
|
||||
propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects,
|
||||
'DOMProperty: Properties that have side effects must use property: %s',
|
||||
@@ -148,7 +141,6 @@ var DOMPropertyInjection = {
|
||||
}
|
||||
},
|
||||
};
|
||||
var defaultValueCache = {};
|
||||
|
||||
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';
|
||||
|
||||
@@ -186,9 +178,6 @@ var DOMProperty = {
|
||||
* mutationMethod:
|
||||
* If non-null, used instead of the property or `setAttribute()` after
|
||||
* initial render.
|
||||
* mustUseAttribute:
|
||||
* Whether the property must be accessed and mutated using `*Attribute()`.
|
||||
* (This includes anything that fails `<propName> in <element>`.)
|
||||
* mustUseProperty:
|
||||
* Whether the property must be accessed and mutated as an object property.
|
||||
* hasSideEffects:
|
||||
@@ -237,27 +226,6 @@ var DOMProperty = {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the default property value for a DOM property (i.e., not an
|
||||
* attribute). Most default values are '' or false, but not all. Worse yet,
|
||||
* some (in particular, `type`) vary depending on the type of element.
|
||||
*
|
||||
* TODO: Is it better to grab all the possible properties when creating an
|
||||
* element to avoid having to create the same element twice?
|
||||
*/
|
||||
getDefaultValueForProperty: function(nodeName, prop) {
|
||||
var nodeDefaults = defaultValueCache[nodeName];
|
||||
var testElement;
|
||||
if (!nodeDefaults) {
|
||||
defaultValueCache[nodeName] = nodeDefaults = {};
|
||||
}
|
||||
if (!(prop in nodeDefaults)) {
|
||||
testElement = document.createElement(nodeName);
|
||||
nodeDefaults[prop] = testElement[prop];
|
||||
}
|
||||
return nodeDefaults[prop];
|
||||
},
|
||||
|
||||
injection: DOMPropertyInjection,
|
||||
};
|
||||
|
||||
|
||||
@@ -168,7 +168,17 @@ var DOMPropertyOperations = {
|
||||
mutationMethod(node, value);
|
||||
} else if (shouldIgnoreValue(propertyInfo, value)) {
|
||||
this.deleteValueForProperty(node, name);
|
||||
} else if (propertyInfo.mustUseAttribute) {
|
||||
} else if (propertyInfo.mustUseProperty) {
|
||||
var propName = propertyInfo.propertyName;
|
||||
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
|
||||
// property type before comparing; only `value` does and is string.
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== ('' + value)) {
|
||||
// Contrary to `setAttribute`, object properties are properly
|
||||
// `toString`ed by IE8/9.
|
||||
node[propName] = value;
|
||||
}
|
||||
} else {
|
||||
var attributeName = propertyInfo.attributeName;
|
||||
var namespace = propertyInfo.attributeNamespace;
|
||||
// `setAttribute` with objects becomes only `[object]` in IE8/9,
|
||||
@@ -181,16 +191,6 @@ var DOMPropertyOperations = {
|
||||
} else {
|
||||
node.setAttribute(attributeName, '' + value);
|
||||
}
|
||||
} else {
|
||||
var propName = propertyInfo.propertyName;
|
||||
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
|
||||
// property type before comparing; only `value` does and is string.
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== ('' + value)) {
|
||||
// Contrary to `setAttribute`, object properties are properly
|
||||
// `toString`ed by IE8/9.
|
||||
node[propName] = value;
|
||||
}
|
||||
}
|
||||
} else if (DOMProperty.isCustomAttribute(name)) {
|
||||
DOMPropertyOperations.setValueForAttribute(node, name, value);
|
||||
@@ -236,18 +236,19 @@ var DOMPropertyOperations = {
|
||||
var mutationMethod = propertyInfo.mutationMethod;
|
||||
if (mutationMethod) {
|
||||
mutationMethod(node, undefined);
|
||||
} else if (propertyInfo.mustUseAttribute) {
|
||||
node.removeAttribute(propertyInfo.attributeName);
|
||||
} else {
|
||||
} else if (propertyInfo.mustUseProperty) {
|
||||
var propName = propertyInfo.propertyName;
|
||||
var defaultValue = DOMProperty.getDefaultValueForProperty(
|
||||
node.nodeName,
|
||||
propName
|
||||
);
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== defaultValue) {
|
||||
node[propName] = defaultValue;
|
||||
if (propertyInfo.hasBooleanValue) {
|
||||
// No HAS_SIDE_EFFECTS logic here, only `value` has it and is string.
|
||||
node[propName] = false;
|
||||
} else {
|
||||
if (!propertyInfo.hasSideEffects ||
|
||||
('' + node[propName]) !== '') {
|
||||
node[propName] = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.removeAttribute(propertyInfo.attributeName);
|
||||
}
|
||||
} else if (DOMProperty.isCustomAttribute(name)) {
|
||||
node.removeAttribute(name);
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
|
||||
var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY;
|
||||
var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE;
|
||||
var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS;
|
||||
@@ -24,19 +22,6 @@ var HAS_POSITIVE_NUMERIC_VALUE =
|
||||
var HAS_OVERLOADED_BOOLEAN_VALUE =
|
||||
DOMProperty.injection.HAS_OVERLOADED_BOOLEAN_VALUE;
|
||||
|
||||
var hasSVG;
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
var implementation = document.implementation;
|
||||
hasSVG = (
|
||||
implementation &&
|
||||
implementation.hasFeature &&
|
||||
implementation.hasFeature(
|
||||
'http://www.w3.org/TR/SVG11/feature#BasicStructure',
|
||||
'1.1'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var HTMLDOMPropertyConfig = {
|
||||
isCustomAttribute: RegExp.prototype.test.bind(
|
||||
new RegExp('^(data|aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$')
|
||||
@@ -49,86 +34,83 @@ var HTMLDOMPropertyConfig = {
|
||||
acceptCharset: null,
|
||||
accessKey: null,
|
||||
action: null,
|
||||
allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
allowTransparency: MUST_USE_ATTRIBUTE,
|
||||
allowFullScreen: HAS_BOOLEAN_VALUE,
|
||||
allowTransparency: null,
|
||||
alt: null,
|
||||
async: HAS_BOOLEAN_VALUE,
|
||||
autoComplete: null,
|
||||
// autoFocus is polyfilled/normalized by AutoFocusUtils
|
||||
// autoFocus: HAS_BOOLEAN_VALUE,
|
||||
autoPlay: HAS_BOOLEAN_VALUE,
|
||||
capture: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
cellPadding: MUST_USE_ATTRIBUTE,
|
||||
cellSpacing: MUST_USE_ATTRIBUTE,
|
||||
challenge: MUST_USE_ATTRIBUTE,
|
||||
charSet: MUST_USE_ATTRIBUTE,
|
||||
capture: HAS_BOOLEAN_VALUE,
|
||||
cellPadding: null,
|
||||
cellSpacing: null,
|
||||
charSet: null,
|
||||
challenge: null,
|
||||
checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
classID: MUST_USE_ATTRIBUTE,
|
||||
// To set className on SVG elements, it's necessary to use .setAttribute;
|
||||
// this works on HTML elements too in all browsers except IE8. Conveniently,
|
||||
// IE8 doesn't support SVG and so we can simply use the attribute in
|
||||
// browsers that support SVG and the property in browsers that don't,
|
||||
// regardless of whether the element is HTML or SVG.
|
||||
className: hasSVG ? MUST_USE_ATTRIBUTE : MUST_USE_PROPERTY,
|
||||
cols: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
|
||||
colSpan: MUST_USE_ATTRIBUTE,
|
||||
classID: null,
|
||||
className: null,
|
||||
cols: HAS_POSITIVE_NUMERIC_VALUE,
|
||||
colSpan: null,
|
||||
content: null,
|
||||
contentEditable: MUST_USE_ATTRIBUTE,
|
||||
contextMenu: MUST_USE_ATTRIBUTE,
|
||||
controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
contentEditable: null,
|
||||
contextMenu: null,
|
||||
controls: HAS_BOOLEAN_VALUE,
|
||||
coords: null,
|
||||
crossOrigin: null,
|
||||
data: null, // For `<object />` acts as `src`.
|
||||
dateTime: MUST_USE_ATTRIBUTE,
|
||||
dateTime: null,
|
||||
default: HAS_BOOLEAN_VALUE,
|
||||
defer: HAS_BOOLEAN_VALUE,
|
||||
dir: MUST_USE_ATTRIBUTE,
|
||||
disabled: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
dir: null,
|
||||
disabled: HAS_BOOLEAN_VALUE,
|
||||
download: HAS_OVERLOADED_BOOLEAN_VALUE,
|
||||
draggable: null,
|
||||
encType: MUST_USE_ATTRIBUTE,
|
||||
form: MUST_USE_ATTRIBUTE,
|
||||
formAction: MUST_USE_ATTRIBUTE,
|
||||
formEncType: MUST_USE_ATTRIBUTE,
|
||||
formMethod: MUST_USE_ATTRIBUTE,
|
||||
encType: null,
|
||||
form: null,
|
||||
formAction: null,
|
||||
formEncType: null,
|
||||
formMethod: null,
|
||||
formNoValidate: HAS_BOOLEAN_VALUE,
|
||||
formTarget: MUST_USE_ATTRIBUTE,
|
||||
frameBorder: MUST_USE_ATTRIBUTE,
|
||||
formTarget: null,
|
||||
frameBorder: null,
|
||||
headers: null,
|
||||
height: MUST_USE_ATTRIBUTE,
|
||||
hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
height: null,
|
||||
hidden: HAS_BOOLEAN_VALUE,
|
||||
high: null,
|
||||
href: null,
|
||||
hrefLang: null,
|
||||
htmlFor: null,
|
||||
httpEquiv: null,
|
||||
icon: null,
|
||||
id: MUST_USE_PROPERTY,
|
||||
inputMode: MUST_USE_ATTRIBUTE,
|
||||
id: null,
|
||||
inputMode: null,
|
||||
integrity: null,
|
||||
is: MUST_USE_ATTRIBUTE,
|
||||
keyParams: MUST_USE_ATTRIBUTE,
|
||||
keyType: MUST_USE_ATTRIBUTE,
|
||||
is: null,
|
||||
keyParams: null,
|
||||
keyType: null,
|
||||
kind: null,
|
||||
label: null,
|
||||
lang: null,
|
||||
list: MUST_USE_ATTRIBUTE,
|
||||
loop: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
list: null,
|
||||
loop: HAS_BOOLEAN_VALUE,
|
||||
low: null,
|
||||
manifest: MUST_USE_ATTRIBUTE,
|
||||
marginHeight: MUST_USE_ATTRIBUTE,
|
||||
marginWidth: MUST_USE_ATTRIBUTE,
|
||||
manifest: null,
|
||||
marginHeight: null,
|
||||
marginWidth: null,
|
||||
max: null,
|
||||
maxLength: MUST_USE_ATTRIBUTE,
|
||||
media: MUST_USE_ATTRIBUTE,
|
||||
maxLength: null,
|
||||
media: null,
|
||||
mediaGroup: null,
|
||||
method: MUST_USE_ATTRIBUTE,
|
||||
method: null,
|
||||
min: null,
|
||||
minLength: MUST_USE_ATTRIBUTE,
|
||||
minLength: null,
|
||||
// Caution; `option.selected` is not updated if `select.multiple` is
|
||||
// disabled with `removeAttribute`.
|
||||
multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
name: null,
|
||||
nonce: MUST_USE_ATTRIBUTE,
|
||||
nonce: null,
|
||||
noValidate: HAS_BOOLEAN_VALUE,
|
||||
open: HAS_BOOLEAN_VALUE,
|
||||
optimum: null,
|
||||
@@ -137,29 +119,29 @@ var HTMLDOMPropertyConfig = {
|
||||
poster: null,
|
||||
preload: null,
|
||||
radioGroup: null,
|
||||
readOnly: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
readOnly: HAS_BOOLEAN_VALUE,
|
||||
rel: null,
|
||||
required: HAS_BOOLEAN_VALUE,
|
||||
reversed: HAS_BOOLEAN_VALUE,
|
||||
role: MUST_USE_ATTRIBUTE,
|
||||
rows: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
|
||||
rowSpan: MUST_USE_ATTRIBUTE | HAS_NUMERIC_VALUE,
|
||||
role: null,
|
||||
rows: HAS_POSITIVE_NUMERIC_VALUE,
|
||||
rowSpan: HAS_NUMERIC_VALUE,
|
||||
sandbox: null,
|
||||
scope: null,
|
||||
scoped: HAS_BOOLEAN_VALUE,
|
||||
scrolling: MUST_USE_ATTRIBUTE,
|
||||
seamless: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
scrolling: null,
|
||||
seamless: HAS_BOOLEAN_VALUE,
|
||||
selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
shape: null,
|
||||
size: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
|
||||
sizes: MUST_USE_ATTRIBUTE,
|
||||
span: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
|
||||
size: HAS_POSITIVE_NUMERIC_VALUE,
|
||||
sizes: null,
|
||||
span: HAS_POSITIVE_NUMERIC_VALUE,
|
||||
spellCheck: null,
|
||||
src: null,
|
||||
srcDoc: MUST_USE_PROPERTY,
|
||||
srcDoc: null,
|
||||
srcLang: null,
|
||||
srcSet: MUST_USE_ATTRIBUTE,
|
||||
start: MUST_USE_ATTRIBUTE | HAS_NUMERIC_VALUE,
|
||||
srcSet: null,
|
||||
start: HAS_NUMERIC_VALUE,
|
||||
step: null,
|
||||
style: null,
|
||||
summary: null,
|
||||
@@ -167,55 +149,55 @@ var HTMLDOMPropertyConfig = {
|
||||
target: null,
|
||||
title: null,
|
||||
// Setting .type throws on non-<input> tags
|
||||
type: MUST_USE_ATTRIBUTE,
|
||||
type: null,
|
||||
useMap: null,
|
||||
value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
|
||||
width: MUST_USE_ATTRIBUTE,
|
||||
wmode: MUST_USE_ATTRIBUTE,
|
||||
wrap: MUST_USE_ATTRIBUTE,
|
||||
width: null,
|
||||
wmode: null,
|
||||
wrap: null,
|
||||
|
||||
/**
|
||||
* RDFa Properties
|
||||
*/
|
||||
about: MUST_USE_ATTRIBUTE,
|
||||
datatype: MUST_USE_ATTRIBUTE,
|
||||
inlist: MUST_USE_ATTRIBUTE,
|
||||
prefix: MUST_USE_ATTRIBUTE,
|
||||
about: null,
|
||||
datatype: null,
|
||||
inlist: null,
|
||||
prefix: null,
|
||||
// property is also supported for OpenGraph in meta tags.
|
||||
property: MUST_USE_ATTRIBUTE,
|
||||
resource: MUST_USE_ATTRIBUTE,
|
||||
typeof: MUST_USE_ATTRIBUTE,
|
||||
vocab: MUST_USE_ATTRIBUTE,
|
||||
property: null,
|
||||
resource: null,
|
||||
typeof: null,
|
||||
vocab: null,
|
||||
|
||||
/**
|
||||
* Non-standard Properties
|
||||
*/
|
||||
// autoCapitalize and autoCorrect are supported in Mobile Safari for
|
||||
// keyboard hints.
|
||||
autoCapitalize: MUST_USE_ATTRIBUTE,
|
||||
autoCorrect: MUST_USE_ATTRIBUTE,
|
||||
autoCapitalize: null,
|
||||
autoCorrect: null,
|
||||
// autoSave allows WebKit/Blink to persist values of input fields on page reloads
|
||||
autoSave: null,
|
||||
// color is for Safari mask-icon link
|
||||
color: null,
|
||||
// itemProp, itemScope, itemType are for
|
||||
// Microdata support. See http://schema.org/docs/gs.html
|
||||
itemProp: MUST_USE_ATTRIBUTE,
|
||||
itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
itemType: MUST_USE_ATTRIBUTE,
|
||||
itemProp: null,
|
||||
itemScope: HAS_BOOLEAN_VALUE,
|
||||
itemType: null,
|
||||
// itemID and itemRef are for Microdata support as well but
|
||||
// only specified in the WHATWG spec document. See
|
||||
// https://html.spec.whatwg.org/multipage/microdata.html#microdata-dom-api
|
||||
itemID: MUST_USE_ATTRIBUTE,
|
||||
itemRef: MUST_USE_ATTRIBUTE,
|
||||
itemID: null,
|
||||
itemRef: null,
|
||||
// results show looking glass icon and recent searches on input
|
||||
// search fields in WebKit/Blink
|
||||
results: null,
|
||||
// IE-only attribute that specifies security restrictions on an iframe
|
||||
// as an alternative to the sandbox attribute on IE<10
|
||||
security: MUST_USE_ATTRIBUTE,
|
||||
security: null,
|
||||
// IE-only attribute that controls focus behavior
|
||||
unselectable: MUST_USE_ATTRIBUTE,
|
||||
unselectable: null,
|
||||
},
|
||||
DOMAttributeNames: {
|
||||
acceptCharset: 'accept-charset',
|
||||
@@ -224,15 +206,6 @@ var HTMLDOMPropertyConfig = {
|
||||
httpEquiv: 'http-equiv',
|
||||
},
|
||||
DOMPropertyNames: {
|
||||
autoComplete: 'autocomplete',
|
||||
autoFocus: 'autofocus',
|
||||
autoPlay: 'autoplay',
|
||||
autoSave: 'autosave',
|
||||
hrefLang: 'hreflang',
|
||||
radioGroup: 'radiogroup',
|
||||
spellCheck: 'spellcheck',
|
||||
srcDoc: 'srcdoc',
|
||||
srcSet: 'srcset',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
|
||||
var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
|
||||
|
||||
var NS = {
|
||||
xlink: 'http://www.w3.org/1999/xlink',
|
||||
xml: 'http://www.w3.org/XML/1998/namespace',
|
||||
@@ -22,30 +18,30 @@ var NS = {
|
||||
|
||||
var SVGDOMPropertyConfig = {
|
||||
Properties: {
|
||||
clipPath: MUST_USE_ATTRIBUTE,
|
||||
fillOpacity: MUST_USE_ATTRIBUTE,
|
||||
fontFamily: MUST_USE_ATTRIBUTE,
|
||||
fontSize: MUST_USE_ATTRIBUTE,
|
||||
markerEnd: MUST_USE_ATTRIBUTE,
|
||||
markerMid: MUST_USE_ATTRIBUTE,
|
||||
markerStart: MUST_USE_ATTRIBUTE,
|
||||
stopColor: MUST_USE_ATTRIBUTE,
|
||||
stopOpacity: MUST_USE_ATTRIBUTE,
|
||||
strokeDasharray: MUST_USE_ATTRIBUTE,
|
||||
strokeLinecap: MUST_USE_ATTRIBUTE,
|
||||
strokeOpacity: MUST_USE_ATTRIBUTE,
|
||||
strokeWidth: MUST_USE_ATTRIBUTE,
|
||||
textAnchor: MUST_USE_ATTRIBUTE,
|
||||
xlinkActuate: MUST_USE_ATTRIBUTE,
|
||||
xlinkArcrole: MUST_USE_ATTRIBUTE,
|
||||
xlinkHref: MUST_USE_ATTRIBUTE,
|
||||
xlinkRole: MUST_USE_ATTRIBUTE,
|
||||
xlinkShow: MUST_USE_ATTRIBUTE,
|
||||
xlinkTitle: MUST_USE_ATTRIBUTE,
|
||||
xlinkType: MUST_USE_ATTRIBUTE,
|
||||
xmlBase: MUST_USE_ATTRIBUTE,
|
||||
xmlLang: MUST_USE_ATTRIBUTE,
|
||||
xmlSpace: MUST_USE_ATTRIBUTE,
|
||||
clipPath: null,
|
||||
fillOpacity: null,
|
||||
fontFamily: null,
|
||||
fontSize: null,
|
||||
markerEnd: null,
|
||||
markerMid: null,
|
||||
markerStart: null,
|
||||
stopColor: null,
|
||||
stopOpacity: null,
|
||||
strokeDasharray: null,
|
||||
strokeLinecap: null,
|
||||
strokeOpacity: null,
|
||||
strokeWidth: null,
|
||||
textAnchor: null,
|
||||
xlinkActuate: null,
|
||||
xlinkArcrole: null,
|
||||
xlinkHref: null,
|
||||
xlinkRole: null,
|
||||
xlinkShow: null,
|
||||
xlinkTitle: null,
|
||||
xlinkType: null,
|
||||
xmlBase: null,
|
||||
xmlLang: null,
|
||||
xmlSpace: null,
|
||||
},
|
||||
DOMAttributeNamespaces: {
|
||||
xlinkActuate: NS.xlink,
|
||||
|
||||
@@ -282,6 +282,23 @@ describe('DOMPropertyOperations', function() {
|
||||
// className should be '', not 'null' or null (which becomes 'null' in
|
||||
// some browsers)
|
||||
expect(stubNode.className).toBe('');
|
||||
expect(stubNode.getAttribute('class')).toBe(null);
|
||||
});
|
||||
|
||||
it('should remove property properly for boolean properties', function() {
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
stubNode,
|
||||
'hidden',
|
||||
true
|
||||
);
|
||||
expect(stubNode.hasAttribute('hidden')).toBe(true);
|
||||
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
stubNode,
|
||||
'hidden',
|
||||
false
|
||||
);
|
||||
expect(stubNode.hasAttribute('hidden')).toBe(false);
|
||||
});
|
||||
|
||||
it('should remove property properly even with different name', function() {
|
||||
@@ -292,6 +309,9 @@ describe('DOMPropertyOperations', function() {
|
||||
DOMPropertyNames: {
|
||||
foobar: 'className',
|
||||
},
|
||||
DOMAttributeNames: {
|
||||
foobar: 'class',
|
||||
},
|
||||
});
|
||||
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
@@ -313,6 +333,53 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('deleteValueForProperty', function() {
|
||||
var stubNode;
|
||||
|
||||
beforeEach(function() {
|
||||
stubNode = document.createElement('div');
|
||||
});
|
||||
|
||||
it('should remove attributes for normal properties', function() {
|
||||
DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'foo');
|
||||
expect(stubNode.getAttribute('title')).toBe('foo');
|
||||
expect(stubNode.title).toBe('foo');
|
||||
|
||||
DOMPropertyOperations.deleteValueForProperty(stubNode, 'title');
|
||||
expect(stubNode.getAttribute('title')).toBe(null);
|
||||
// JSDOM does not behave correctly for attributes/properties
|
||||
//expect(stubNode.title).toBe('');
|
||||
});
|
||||
|
||||
it('should not remove attributes for special properties', function() {
|
||||
stubNode = document.createElement('input');
|
||||
stubNode.setAttribute('value', 'foo');
|
||||
|
||||
DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
|
||||
// JSDOM does not behave correctly for attributes/properties
|
||||
//expect(stubNode.getAttribute('value')).toBe('foo');
|
||||
expect(stubNode.value).toBe('');
|
||||
});
|
||||
|
||||
it('should not leave all options selected when deleting multiple', function() {
|
||||
stubNode = document.createElement('select');
|
||||
stubNode.multiple = true;
|
||||
stubNode.appendChild(document.createElement('option'));
|
||||
stubNode.appendChild(document.createElement('option'));
|
||||
stubNode.options[0].selected = true;
|
||||
stubNode.options[1].selected = true;
|
||||
|
||||
DOMPropertyOperations.deleteValueForProperty(stubNode, 'multiple');
|
||||
expect(stubNode.getAttribute('multiple')).toBe(null);
|
||||
expect(stubNode.multiple).toBe(false);
|
||||
|
||||
expect(
|
||||
stubNode.options[0].selected &&
|
||||
stubNode.options[1].selected
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectDOMPropertyConfig', function() {
|
||||
it('should support custom attributes', function() {
|
||||
// foobar does not exist yet
|
||||
|
||||
@@ -576,7 +576,45 @@ describe('ReactDOMComponent', function() {
|
||||
expect(container.textContent).toEqual('bonjour');
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations', function() {
|
||||
it('should not incur unnecessary DOM mutations for attributes', function() {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<div id="" />, container);
|
||||
|
||||
var node = container.firstChild;
|
||||
var nodeSetAttribute = node.setAttribute;
|
||||
node.setAttribute = jest.genMockFn();
|
||||
node.setAttribute.mockImpl(nodeSetAttribute);
|
||||
|
||||
var nodeRemoveAttribute = node.removeAttribute;
|
||||
node.removeAttribute = jest.genMockFn();
|
||||
node.removeAttribute.mockImpl(nodeRemoveAttribute);
|
||||
|
||||
ReactDOM.render(<div id="" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(0);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
|
||||
ReactDOM.render(<div id="foo" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
|
||||
ReactDOM.render(<div id="foo" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(1);
|
||||
|
||||
ReactDOM.render(<div id="" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(2);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(1);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(2);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for string properties', function() {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<div value="" />, container);
|
||||
|
||||
@@ -595,8 +633,52 @@ describe('ReactDOMComponent', function() {
|
||||
ReactDOM.render(<div value="" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
|
||||
ReactDOM.render(<div value="foo" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
|
||||
ReactDOM.render(<div value="foo" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
|
||||
ReactDOM.render(<div value={null} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
|
||||
ReactDOM.render(<div value="" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for boolean properties', function() {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<div checked={true} />, container);
|
||||
|
||||
var node = container.firstChild;
|
||||
var nodeValue = true;
|
||||
var nodeValueSetter = jest.genMockFn();
|
||||
Object.defineProperty(node, 'checked', {
|
||||
get: function() {
|
||||
return nodeValue;
|
||||
},
|
||||
set: nodeValueSetter.mockImplementation(function(newValue) {
|
||||
nodeValue = newValue;
|
||||
}),
|
||||
});
|
||||
|
||||
ReactDOM.render(<div checked={true} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
|
||||
ReactDOM.render(<div checked={false} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2); // should be 1
|
||||
|
||||
ReactDOM.render(<div checked={true} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should ignore attribute whitelist for elements with the "is: attribute', function() {
|
||||
|
||||
Reference in New Issue
Block a user