diff --git a/packages/dom-helper/lib/prop.js b/packages/dom-helper/lib/prop.js index 8268cfad..a78bb6a6 100644 --- a/packages/dom-helper/lib/prop.js +++ b/packages/dom-helper/lib/prop.js @@ -2,6 +2,8 @@ export function isAttrRemovalValue(value) { return value === null || value === undefined; } +function UNDEFINED() {} + // TODO should this be an o_create kind of thing? export var propertyCaches = {}; @@ -13,11 +15,42 @@ export function normalizeProperty(element, attrName) { // TODO should this be an o_create kind of thing? cache = {}; for (key in element) { - cache[key.toLowerCase()] = key; + key = key.toLowerCase(); + if (isSettable(element, key)) { + cache[key] = key; + } else { + cache[key] = UNDEFINED; + } } propertyCaches[tagName] = cache; } // presumes that the attrName has been lowercased. - return cache[attrName]; + var value = cache[attrName]; + return value === UNDEFINED ? undefined : value; +} + +// elements with a property that does not conform to the spec in certain +// browsers. In these cases, we'll end up using setAttribute instead +var badPairs = [{ + // phantomjs < 2.0 lets you set it as a prop but won't reflect it + // back to the attribute. button.getAttribute('type') === null + tagName: 'BUTTON', + propName: 'type' +}, { + // Some version of IE (like IE9) actually throw an exception + // if you set input.type = 'something-unknown' + tagName: 'INPUT', + propName: 'type' +}]; + +function isSettable(element, attrName) { + for (let i = 0, l = badPairs.length; i < l; i++) { + let pair = badPairs[i]; + if (pair.tagName === element.tagName && pair.propName === attrName) { + return false; + } + } + + return true; } diff --git a/packages/dom-helper/tests/dom-helper-test.js b/packages/dom-helper/tests/dom-helper-test.js index 937221e3..9222fb5d 100644 --- a/packages/dom-helper/tests/dom-helper-test.js +++ b/packages/dom-helper/tests/dom-helper-test.js @@ -194,6 +194,36 @@ test('#setProperty removes attr with undefined', function(){ equalHTML(node, '
', 'undefined attribute removes the attribute'); }); +test('#setProperty uses setAttribute for special non-compliant element props', function() { + expect(6); + + var badPairs = [ + { tagName: 'button', key: 'type', value: 'submit', selfClosing: false }, + { tagName: 'input', key: 'type', value: 'x-not-supported', selfClosing: true } + ]; + + badPairs.forEach(function(pair) { + var node = dom.createElement(pair.tagName); + var setAttribute = node.setAttribute; + + node.setAttribute = function(attrName, value) { + equal(attrName, pair.key, 'setAttribute called with correct attrName'); + equal(value, pair.value, 'setAttribute called with correct value'); + return setAttribute.call(this, attrName, value); + }; + + dom.setProperty(node, pair.key, pair.value); + + // e.g. + var expected = '<' + pair.tagName + ' ' + pair.key + '="' + pair.value + '">'; + if (pair.selfClosing === false) { + expected += ''; + } + + equalHTML(node, expected, 'output html is correct'); + }); +}); + test('#addClasses', function(){ var node = dom.createElement('div'); dom.addClasses(node, ['super-fun']); diff --git a/packages/dom-helper/tests/prop-test.js b/packages/dom-helper/tests/prop-test.js new file mode 100644 index 00000000..ed78ebac --- /dev/null +++ b/packages/dom-helper/tests/prop-test.js @@ -0,0 +1,27 @@ +import { normalizeProperty } from 'dom-helper/prop'; + +QUnit.module('dom-helper prop'); + +test('returns `undefined` for special element properties that are non-compliant in certain browsers', function() { + expect(2); + + var badPairs = [ + { tagName: 'BUTTON', key: 'type' }, + { tagName: 'INPUT', key: 'type' } + ]; + + badPairs.forEach(function(pair) { + var element = { + tagName: pair.tagName + }; + + Object.defineProperty(element, pair.key, { + set: function() { + throw new Error('I am a bad browser!'); + } + }); + + var actual = normalizeProperty(element, pair.key); + equal(actual, undefined); + }); +}); \ No newline at end of file