diff --git a/packages/dom-helper/lib/main.js b/packages/dom-helper/lib/main.js index 184d56e1..f891f527 100644 --- a/packages/dom-helper/lib/main.js +++ b/packages/dom-helper/lib/main.js @@ -47,16 +47,16 @@ var canClone = doc && (function(document){ // This is not the namespace of the element, but of // the elements inside that elements. -function interiorNamespace(element){ +function interiorNamespace(element) { if ( element && element.namespaceURI === svgNamespace && !svgHTMLIntegrationPoints[element.tagName] ) { return svgNamespace; - } else { - return null; } + + return null; } // The HTML spec allows for "omitted start tags". These tags are optional @@ -351,19 +351,18 @@ if (doc && doc.createElementNS) { // Only opt into namespace detection if a contextualElement // is passed. prototype.createElement = function(tagName, contextualElement) { - var namespace = this.namespace; + let namespace = this.namespace; if (contextualElement) { if (tagName === 'svg') { namespace = svgNamespace; } else { - namespace = interiorNamespace(contextualElement); + namespace = typeof contextualElement === 'string' ? contextualElement : interiorNamespace(contextualElement); } } if (namespace) { return this.document.createElementNS(namespace, tagName); - } else { - return this.document.createElement(tagName); } + return this.document.createElement(tagName); }; prototype.setAttributeNS = function(element, namespace, name, value) { element.setAttributeNS(namespace, name, String(value)); diff --git a/packages/htmlbars-compiler/lib/fragment-javascript-compiler.js b/packages/htmlbars-compiler/lib/fragment-javascript-compiler.js index e51df292..294790b1 100644 --- a/packages/htmlbars-compiler/lib/fragment-javascript-compiler.js +++ b/packages/htmlbars-compiler/lib/fragment-javascript-compiler.js @@ -32,13 +32,21 @@ FragmentJavaScriptCompiler.prototype.createFragment = function() { this.source.push(this.indent+' var '+el+' = dom.createDocumentFragment();\n'); }; -FragmentJavaScriptCompiler.prototype.createElement = function(tagName) { - var el = 'el'+(++this.depth); +FragmentJavaScriptCompiler.prototype.createElement = function(tagName, contextualElement) { + let el = 'el'+(++this.depth); if (tagName === 'svg') { this.pushNamespaceFrame({namespace: svgNamespace, depth: this.depth}); } this.ensureNamespace(); - this.source.push(this.indent+' var '+el+' = dom.createElement('+string(tagName)+');\n'); + + let toPush = this.indent+' var '+el+' = dom.createElement('+string(tagName); + if (contextualElement) { + toPush += ', ' + string(contextualElement); + } + toPush += ');\n'; + + this.source.push(toPush); + if (svgHTMLIntegrationPoints[tagName]) { this.pushNamespaceFrame({namespace: null, depth: this.depth}); } @@ -60,12 +68,11 @@ FragmentJavaScriptCompiler.prototype.returnNode = function() { }; FragmentJavaScriptCompiler.prototype.setAttribute = function(name, value, namespace) { - var el = 'el'+this.depth; + let el = 'el'+this.depth; if (namespace) { - this.source.push(this.indent+' dom.setAttributeNS('+el+','+string(namespace)+','+string(name)+','+string(value)+');\n'); - } else { - this.source.push(this.indent+' dom.setAttribute('+el+','+string(name)+','+string(value)+');\n'); + return this.source.push(this.indent+' dom.setAttributeNS('+el+','+string(namespace)+','+string(name)+','+string(value)+');\n'); } + this.source.push(this.indent+' dom.setAttribute('+el+','+string(name)+','+string(value)+');\n'); }; FragmentJavaScriptCompiler.prototype.appendChild = function() { diff --git a/packages/htmlbars-compiler/lib/fragment-opcode-compiler.js b/packages/htmlbars-compiler/lib/fragment-opcode-compiler.js index a1d867f5..461b6507 100644 --- a/packages/htmlbars-compiler/lib/fragment-opcode-compiler.js +++ b/packages/htmlbars-compiler/lib/fragment-opcode-compiler.js @@ -33,7 +33,17 @@ FragmentOpcodeCompiler.prototype.comment = function(comment) { }; FragmentOpcodeCompiler.prototype.openElement = function(element) { - this.opcode('createElement', [element.tag]); + let contextualElement; + + for (let i = 0; i < element.attributes.length; i++) { + let attr = element.attributes[i]; + if (attr.name === 'is') { + contextualElement = attr.value.chars; + break; + } + } + + this.opcode('createElement', [element.tag, contextualElement]); forEach(element.attributes, this.attribute, this); }; diff --git a/packages/htmlbars-compiler/tests/fragment-javascript-compiler-test.js b/packages/htmlbars-compiler/tests/fragment-javascript-compiler-test.js new file mode 100644 index 00000000..a3919f2f --- /dev/null +++ b/packages/htmlbars-compiler/tests/fragment-javascript-compiler-test.js @@ -0,0 +1,38 @@ +import FragmentJavaScriptCompiler from "../htmlbars-compiler/fragment-javascript-compiler"; + +QUnit.module('FragmentJavaScriptCompiler'); + +test('createElement correctly generates code for tagName with contextualElement', assert => { + assert.expect(1); + let fragmentCompiler = new FragmentJavaScriptCompiler(); + fragmentCompiler.source.length = 0; + fragmentCompiler.depth = -1; + fragmentCompiler.indent = ""; + fragmentCompiler.namespaceFrameStack = [{namespace: null, depth: null}]; + + const tagName = 'button'; + const contextualElement = 'my-button'; + + fragmentCompiler.createElement(tagName, contextualElement); + + const pattern = new RegExp(`dom\\.createElement\\("${tagName}", "${contextualElement}"\\)`); + + assert.equal(pattern.test(fragmentCompiler.source[1]), true); +}); + +test('createElement correctly generates code for tagName without contextualElement', assert => { + assert.expect(1); + let fragmentCompiler = new FragmentJavaScriptCompiler(); + fragmentCompiler.source.length = 0; + fragmentCompiler.depth = -1; + fragmentCompiler.indent = ""; + fragmentCompiler.namespaceFrameStack = [{namespace: null, depth: null}]; + + const tagName = 'button'; + + fragmentCompiler.createElement(tagName); + + const pattern = new RegExp(`dom\\.createElement\\("${tagName}"\\)`); + + assert.equal(pattern.test(fragmentCompiler.source[1]), true); +}); diff --git a/packages/htmlbars-compiler/tests/fragment-opcode-compiler-test.js b/packages/htmlbars-compiler/tests/fragment-opcode-compiler-test.js new file mode 100644 index 00000000..ec678f12 --- /dev/null +++ b/packages/htmlbars-compiler/tests/fragment-opcode-compiler-test.js @@ -0,0 +1,32 @@ +import FragmentOpcodeCompiler from "../htmlbars-compiler/fragment-opcode-compiler"; + +QUnit.module('FragmentOpcodeCompiler'); + +test('openElement calls opcode with correct arguments when element has `is` attribute', assert => { + assert.expect(2); + let opcodeCompiler = new FragmentOpcodeCompiler(); + + const tagName = 'button'; + const typeExtension = 'my-button'; + const attributeName = 'is'; + + const element = { + tag: tagName, + attributes: [ + { + name: attributeName, + value: { + chars: typeExtension + } + } + ] + }; + + // monkey patch + opcodeCompiler.opcode = (opname, args) => { + assert.equal(opname, 'createElement'); + assert.deepEqual(args, [ tagName, typeExtension ]); + }; + + opcodeCompiler.openElement(element); +}); diff --git a/packages/htmlbars-compiler/tests/html-compiler-test.js b/packages/htmlbars-compiler/tests/html-compiler-test.js index 6e6074a5..5f143cd0 100644 --- a/packages/htmlbars-compiler/tests/html-compiler-test.js +++ b/packages/htmlbars-compiler/tests/html-compiler-test.js @@ -719,6 +719,22 @@ test("Simple elements can have dashed attributes", function() { equalTokens(fragment, '