diff --git a/src/dom/attributes/BooleanAttributes.js b/src/dom/attributes/BooleanAttributes.js
new file mode 100644
index 0000000..9083b6a
--- /dev/null
+++ b/src/dom/attributes/BooleanAttributes.js
@@ -0,0 +1,33 @@
+/**
+ * List of boolean attributes
+ * These attributes should have their React attribute value set to be the same as their name
+ * E.g. =
+ * =
+ * =
+ * @type {Array}
+ */
+export default [
+ 'allowfullScreen',
+ 'async',
+ 'autoplay',
+ 'capture',
+ 'checked',
+ 'controls',
+ 'default',
+ 'defer',
+ 'disabled',
+ 'formnovalidate',
+ 'hidden',
+ 'loop',
+ 'multiple',
+ 'muted',
+ 'novalidate',
+ 'open',
+ 'readonly',
+ 'required',
+ 'reversed',
+ 'scoped',
+ 'seamless',
+ 'selected',
+ 'itemscope'
+];
diff --git a/src/dom/attributes/ReactAttributes.js b/src/dom/attributes/ReactAttributes.js
new file mode 100644
index 0000000..e39d124
--- /dev/null
+++ b/src/dom/attributes/ReactAttributes.js
@@ -0,0 +1,158 @@
+/**
+ * Mapping of standard HTML attributes to their React counterparts
+ * List taken and reversed from react/src/renderers/dom/shared/HTMLDOMPropertyConfig.js
+ * https://github.com/facebook/react/blob/c9c3c339b757682f1154f1c915eb55e6a8766933/src/renderers/dom/shared/HTMLDOMPropertyConfig.js
+ * @type {Object}
+ */
+export default {
+ /**
+ * Standard Properties
+ */
+ accept: 'accept',
+ 'accept-charset': 'acceptCharset',
+ accesskey: 'accessKey',
+ action: 'action',
+ allowfullscreen: 'allowFullScreen',
+ allowtransparency: 'allowTransparency',
+ alt: 'alt',
+ async: 'async',
+ autocomplete: 'autoComplete',
+ autoplay: 'autoPlay',
+ capture: 'capture',
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing',
+ charset: 'charSet',
+ challenge: 'challenge',
+ checked: 'checked',
+ classid: 'classID',
+ class: 'className',
+ cols: 'cols',
+ colspan: 'colSpan',
+ content: 'content',
+ contenteditable: 'contentEditable',
+ contextmenu: 'contextMenu',
+ controls: 'controls',
+ coords: 'coords',
+ crossorigin: 'crossOrigin',
+ data: 'data',
+ datetime: 'dateTime',
+ default: 'default',
+ defer: 'defer',
+ dir: 'dir',
+ disabled: 'disabled',
+ download: 'download',
+ draggable: 'draggable',
+ enctype: 'encType',
+ form: 'form',
+ formaction: 'formAction',
+ formenctype: 'formEncType',
+ formmethod: 'formMethod',
+ formnovalidate: 'formNoValidate',
+ formtarget: 'formTarget',
+ frameborder: 'frameBorder',
+ headers: 'headers',
+ height: 'height',
+ hidden: 'hidden',
+ high: 'high',
+ href: 'href',
+ hreflang: 'hrefLang',
+ for: 'htmlFor',
+ 'http-equiv': 'httpEquiv',
+ icon: 'icon',
+ id: 'id',
+ inputmode: 'inputMode',
+ integrity: 'integrity',
+ is: 'is',
+ keyparams: 'keyParams',
+ keytype: 'keyType',
+ kind: 'kind',
+ label: 'label',
+ lang: 'lang',
+ list: 'list',
+ loop: 'loop',
+ low: 'low',
+ manifest: 'manifest',
+ marginheight: 'marginHeight',
+ marginwidth: 'marginWidth',
+ max: 'max',
+ maxlength: 'maxLength',
+ media: 'media',
+ mediagroup: 'mediaGroup',
+ method: 'method',
+ min: 'min',
+ minlength: 'minLength',
+ multiple: 'multiple',
+ muted: 'muted',
+ name: 'name',
+ nonce: 'nonce',
+ novalidate: 'noValidate',
+ open: 'open',
+ optimum: 'optimum',
+ pattern: 'pattern',
+ placeholder: 'placeholder',
+ poster: 'poster',
+ preload: 'preload',
+ radiogroup: 'radioGroup',
+ readonly: 'readOnly',
+ rel: 'rel',
+ required: 'required',
+ reversed: 'reversed',
+ role: 'role',
+ rows: 'rows',
+ rowspan: 'rowSpan',
+ sandbox: 'sandbox',
+ scope: 'scope',
+ scoped: 'scoped',
+ scrolling: 'scrolling',
+ seamless: 'seamless',
+ selected: 'selected',
+ shape: 'shape',
+ size: 'size',
+ sizes: 'sizes',
+ span: 'span',
+ spellcheck: 'spellCheck',
+ src: 'src',
+ srcdoc: 'srcDoc',
+ srclang: 'srcLang',
+ srcset: 'srcSet',
+ start: 'start',
+ step: 'step',
+ style: 'style',
+ summary: 'summary',
+ tabindex: 'tabIndex',
+ target: 'target',
+ title: 'title',
+ type: 'type',
+ usemap: 'useMap',
+ value: 'value',
+ width: 'width',
+ wmode: 'wmode',
+ wrap: 'wrap',
+ /**
+ * RDFa Properties
+ */
+ about: 'about',
+ datatype: 'datatype',
+ inlist: 'inlist',
+ prefix: 'prefix',
+ property: 'property',
+ resource: 'resource',
+ typeof: 'typeof',
+ vocab: 'vocab',
+ /**
+ * Non-standard Properties
+ */
+ autocapitalize: 'autoCapitalize',
+ autocorrect: 'autoCorrect',
+ autosave: 'autoSave',
+ color: 'color',
+ itemprop: 'itemProp',
+ itemscope: 'itemScope',
+ itemtype: 'itemType',
+ itemid: 'itemID',
+ itemref: 'itemRef',
+ results: 'results',
+ security: 'security',
+ unselectable: 'unselectable',
+ autofocus: 'autoFocus'
+};
diff --git a/src/utils/isVoidElement.js b/src/dom/elements/VoidElements.js
similarity index 57%
rename from src/utils/isVoidElement.js
rename to src/dom/elements/VoidElements.js
index 5b3fa09..39cd398 100644
--- a/src/utils/isVoidElement.js
+++ b/src/dom/elements/VoidElements.js
@@ -1,4 +1,9 @@
-const voidElements = [
+/**
+ * List of void elements
+ * These elements are not allowed to have children
+ * @type {Array}
+ */
+export default [
'area',
'base',
'br',
@@ -16,9 +21,3 @@ const voidElements = [
'track',
'wbr'
];
-
-export default function isVoidElement(element) {
-
- return voidElements.indexOf(element) >= 0;
-
-}
diff --git a/src/elementTypes/TagElementType.js b/src/elementTypes/TagElementType.js
index 6109dd9..7db95d1 100644
--- a/src/elementTypes/TagElementType.js
+++ b/src/elementTypes/TagElementType.js
@@ -2,7 +2,7 @@ import React from 'react';
import ProcessNodes from '../utils/ProcessNodes';
import GeneratePropsFromAttributes from '../utils/GeneratePropsFromAttributes';
import TransformTagName from '../utils/TransformTagName';
-import isVoidElement from '../utils/isVoidElement';
+import VoidElements from '../dom/elements/VoidElements';
/**
* Converts any element (excluding style - see StyleElementType - and script) to a react element.
@@ -21,7 +21,7 @@ export default function TagElementType(node, key) {
// If the node is not a void element and has children then process them
let children = null;
- if (!isVoidElement(tagName)) {
+ if (VoidElements.indexOf(tagName) === -1) {
children = ProcessNodes(node.children);
}
diff --git a/src/utils/HtmlAttributesToReact.js b/src/utils/HtmlAttributesToReact.js
index 0ad2c13..5a4987e 100644
--- a/src/utils/HtmlAttributesToReact.js
+++ b/src/utils/HtmlAttributesToReact.js
@@ -1,160 +1,23 @@
+import BooleanAttributes from '../dom/attributes/BooleanAttributes';
+import ReactAttributes from '../dom/attributes/ReactAttributes';
+
/**
- * Mapping of standard HTML attributes to their React counterparts
- * List taken and reversed from react/src/renderers/dom/shared/HTMLDOMPropertyConfig.js
- * https://github.com/facebook/react/blob/c9c3c339b757682f1154f1c915eb55e6a8766933/src/renderers/dom/shared/HTMLDOMPropertyConfig.js
- * @type {Object}
+ * Returns the parsed attribute value taking into account things like boolean attributes
+ *
+ * @param {String} attribute The name of the attribute
+ * @param {*} value The value of the attribute from the HTML
+ * @returns {*} The parsed attribute value
*/
-const attributeMap = {
- /**
- * Standard Properties
- */
- accept: 'accept',
- 'accept-charset': 'acceptCharset',
- accesskey: 'accessKey',
- action: 'action',
- allowfullscreen: 'allowFullScreen',
- allowtransparency: 'allowTransparency',
- alt: 'alt',
- async: 'async',
- autocomplete: 'autoComplete',
- autoplay: 'autoPlay',
- capture: 'capture',
- cellpadding: 'cellPadding',
- cellspacing: 'cellSpacing',
- charset: 'charSet',
- challenge: 'challenge',
- checked: 'checked',
- classid: 'classID',
- class: 'className',
- cols: 'cols',
- colspan: 'colSpan',
- content: 'content',
- contenteditable: 'contentEditable',
- contextmenu: 'contextMenu',
- controls: 'controls',
- coords: 'coords',
- crossorigin: 'crossOrigin',
- data: 'data',
- datetime: 'dateTime',
- default: 'default',
- defer: 'defer',
- dir: 'dir',
- disabled: 'disabled',
- download: 'download',
- draggable: 'draggable',
- enctype: 'encType',
- form: 'form',
- formaction: 'formAction',
- formenctype: 'formEncType',
- formmethod: 'formMethod',
- formnovalidate: 'formNoValidate',
- formtarget: 'formTarget',
- frameborder: 'frameBorder',
- headers: 'headers',
- height: 'height',
- hidden: 'hidden',
- high: 'high',
- href: 'href',
- hreflang: 'hrefLang',
- for: 'htmlFor',
- 'http-equiv': 'httpEquiv',
- icon: 'icon',
- id: 'id',
- inputmode: 'inputMode',
- integrity: 'integrity',
- is: 'is',
- keyparams: 'keyParams',
- keytype: 'keyType',
- kind: 'kind',
- label: 'label',
- lang: 'lang',
- list: 'list',
- loop: 'loop',
- low: 'low',
- manifest: 'manifest',
- marginheight: 'marginHeight',
- marginwidth: 'marginWidth',
- max: 'max',
- maxlength: 'maxLength',
- media: 'media',
- mediagroup: 'mediaGroup',
- method: 'method',
- min: 'min',
- minlength: 'minLength',
- multiple: 'multiple',
- muted: 'muted',
- name: 'name',
- nonce: 'nonce',
- novalidate: 'noValidate',
- open: 'open',
- optimum: 'optimum',
- pattern: 'pattern',
- placeholder: 'placeholder',
- poster: 'poster',
- preload: 'preload',
- radiogroup: 'radioGroup',
- readonly: 'readOnly',
- rel: 'rel',
- required: 'required',
- reversed: 'reversed',
- role: 'role',
- rows: 'rows',
- rowspan: 'rowSpan',
- sandbox: 'sandbox',
- scope: 'scope',
- scoped: 'scoped',
- scrolling: 'scrolling',
- seamless: 'seamless',
- selected: 'selected',
- shape: 'shape',
- size: 'size',
- sizes: 'sizes',
- span: 'span',
- spellcheck: 'spellCheck',
- src: 'src',
- srcdoc: 'srcDoc',
- srclang: 'srcLang',
- srcset: 'srcSet',
- start: 'start',
- step: 'step',
- style: 'style',
- summary: 'summary',
- tabindex: 'tabIndex',
- target: 'target',
- title: 'title',
- type: 'type',
- usemap: 'useMap',
- value: 'value',
- width: 'width',
- wmode: 'wmode',
- wrap: 'wrap',
- /**
- * RDFa Properties
- */
- about: 'about',
- datatype: 'datatype',
- inlist: 'inlist',
- prefix: 'prefix',
- property: 'property',
- resource: 'resource',
- typeof: 'typeof',
- vocab: 'vocab',
- /**
- * Non-standard Properties
- */
- autocapitalize: 'autoCapitalize',
- autocorrect: 'autoCorrect',
- autosave: 'autoSave',
- color: 'color',
- itemprop: 'itemProp',
- itemscope: 'itemScope',
- itemtype: 'itemType',
- itemid: 'itemID',
- itemref: 'itemRef',
- results: 'results',
- security: 'security',
- unselectable: 'unselectable',
- autofocus: 'autoFocus'
+const getParsedAttributeValue = function(attribute, value) {
+
+ // if the attribute if a boolean then it's value should be the same as it's name
+ // e.g. disabled="disabled"
+ if (BooleanAttributes.indexOf(attribute) >= 0) {
+ value = attribute;
+ }
+
+ return value;
+
};
/**
@@ -170,11 +33,18 @@ export default function HtmlAttributesToReact(attributes) {
.keys(attributes)
.reduce(
(mappedAttributes, attribute) => {
+
// lowercase the attribute name and find it in the react attribute map
const lowerCaseAttribute = attribute.toLowerCase();
- const key = attributeMap[lowerCaseAttribute] || lowerCaseAttribute;
- mappedAttributes[key] = attributes[attribute];
+
+ // format the attribute name
+ const name = ReactAttributes[lowerCaseAttribute] || lowerCaseAttribute;
+
+ // add the parsed attribute value to the mapped attributes
+ mappedAttributes[name] = getParsedAttributeValue(name, attributes[attribute]);
+
return mappedAttributes;
+
},
{}
);
diff --git a/test/integration/integration.spec.js b/test/integration/integration.spec.js
index 29aa92b..40f6ced 100644
--- a/test/integration/integration.spec.js
+++ b/test/integration/integration.spec.js
@@ -77,4 +77,10 @@ describe('Integration tests: ', () => {
test('
test
', 'test
'); }); + it('should convert boolean attribute values', () => { + test('', ''); + test('', ''); + test('', ''); + }); + }); diff --git a/test/unit/elementTypes/TagElementType.spec.js b/test/unit/elementTypes/TagElementType.spec.js index ed21742..f706438 100644 --- a/test/unit/elementTypes/TagElementType.spec.js +++ b/test/unit/elementTypes/TagElementType.spec.js @@ -1,11 +1,11 @@ const GeneratePropsFromAttributes = jasmine.createSpy('GeneratePropsFromAttributes').and.callFake(attrs => attrs); const ProcessNodes = jasmine.createSpy('ProcessNodes').and.returnValue('children'); -const isVoidElement = jasmine.createSpy('isVoidElement').and.returnValue(false); +const VoidElements = ['void']; const TagElementType = require('inject!elementTypes/TagElementType')({ '../utils/GeneratePropsFromAttributes': GeneratePropsFromAttributes, '../utils/ProcessNodes': ProcessNodes, - '../utils/isVoidElement': isVoidElement + '../dom/elements/VoidElements': VoidElements }).default; describe('Testing `elementTypes/TagElementType', () => { @@ -13,7 +13,6 @@ describe('Testing `elementTypes/TagElementType', () => { beforeEach(() => { GeneratePropsFromAttributes.calls.reset(); ProcessNodes.calls.reset(); - isVoidElement.calls.reset(); }); it('should return a React element corresponding to the node name', () => { @@ -45,7 +44,6 @@ describe('Testing `elementTypes/TagElementType', () => { }, children: 'child' }; - isVoidElement.and.returnValue(true); const voidElement = TagElementType(voidNode, 'key'); expect(voidElement.type).toBe('void'); diff --git a/test/unit/utils/HtmlAttributesToReact.spec.js b/test/unit/utils/HtmlAttributesToReact.spec.js index c3dd310..db074f6 100644 --- a/test/unit/utils/HtmlAttributesToReact.spec.js +++ b/test/unit/utils/HtmlAttributesToReact.spec.js @@ -19,7 +19,10 @@ describe('Testing `utils/HtmlAttributesToReact`', () => { 'aria-role': 'role', // it should also use non specified attributes (although react will filter these out) testattribute: 'testAttribute', - 'UPPER-CASE-TEST-ATTRIBUTE': 'upperTestAttribute' + 'UPPER-CASE-TEST-ATTRIBUTE': 'upperTestAttribute', + // boolean attributes + disabled: '', + checked: '' }; const expectedReactAttributes = { @@ -32,7 +35,9 @@ describe('Testing `utils/HtmlAttributesToReact`', () => { 'data-test': 'test', 'aria-role': 'role', testattribute: 'testAttribute', - 'upper-case-test-attribute': 'upperTestAttribute' + 'upper-case-test-attribute': 'upperTestAttribute', + disabled: 'disabled', + checked: 'checked' }; expect(HtmlAttributesToReact(htmlAttributes)).toEqual(expectedReactAttributes); diff --git a/test/unit/utils/isVoidElement.spec.js b/test/unit/utils/isVoidElement.spec.js deleted file mode 100644 index 4dda1cb..0000000 --- a/test/unit/utils/isVoidElement.spec.js +++ /dev/null @@ -1,12 +0,0 @@ -import isVoidElement from 'utils/isVoidElement'; - -describe('Testing `utils/isVoidElement`', () => { - - it('should return whether the element is a void element', () => { - expect(isVoidElement('img')).toBe(true); - expect(isVoidElement('br')).toBe(true); - expect(isVoidElement('div')).toBe(false); - expect(isVoidElement('p')).toBe(false); - }); - -});