diff --git a/package-lock.json b/package-lock.json index 2c5681328..0deff4d24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "marko", - "version": "4.12.5", + "version": "4.12.5-2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b95d40156..f182ca93e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "marko", - "version": "4.12.5", + "version": "4.12.5-2", "license": "MIT", "description": "UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.", "scripts": { diff --git a/src/compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js b/src/compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js index 74698d817..bd54495d8 100644 --- a/src/compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js +++ b/src/compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js @@ -84,6 +84,7 @@ class HtmlElementVDOM extends Node { this.dynamicAttributes = def.dynamicAttributes; this.key = def.key; this.runtimeFlags = def.runtimeFlags; + this.isAutoKeyed = def.isAutoKeyed; this.isSVG = false; this.isTextArea = false; @@ -279,7 +280,10 @@ class HtmlElementVDOM extends Node { createArgs[INDEX_ATTRS] = attributesArg; } - if (key) { + if ( + key && + (!this.isAutoKeyed || !this.isStatic || this.createElementId) + ) { createArgs[INDEX_KEY] = key; if (!this.isStatic) { diff --git a/src/compiler/ast/HtmlElement/vdom/generateCode.js b/src/compiler/ast/HtmlElement/vdom/generateCode.js index ef5cb4686..2ad614731 100644 --- a/src/compiler/ast/HtmlElement/vdom/generateCode.js +++ b/src/compiler/ast/HtmlElement/vdom/generateCode.js @@ -39,6 +39,7 @@ module.exports = function(node, codegen, vdomUtil) { var properties = codegen.generateCode(node.getProperties()); var dynamicAttributes = codegen.generateCode(node.dynamicAttributes); var key = node.key; + var isAutoKeyed = node.isAutoKeyed; var runtimeFlags = node.runtimeFlags; var nextConstId = node.nextConstId; @@ -91,7 +92,8 @@ module.exports = function(node, codegen, vdomUtil) { isHtmlOnly, dynamicAttributes, nextConstId, - runtimeFlags + runtimeFlags, + isAutoKeyed }); if (bodyOnlyIf) { diff --git a/src/components/Component.js b/src/components/Component.js index c2c551d73..55ed56706 100644 --- a/src/components/Component.js +++ b/src/components/Component.js @@ -140,12 +140,6 @@ function checkInputChanged(existingComponent, oldInput, newInput) { return false; } -function getNodes(component) { - var nodes = []; - component.___forEachNode(nodes.push.bind(nodes)); - return nodes; -} - var componentProto; /** @@ -157,8 +151,7 @@ function Component(id) { EventEmitter.call(this); this.id = id; this.___state = null; - this.___startNode = null; - this.___endNode = null; + this.___rootNode = null; this.___subscriptions = null; this.___domEventListenerHandles = null; this.___bubblingDomEvents = null; // Used to keep track of bubbling DOM events for components rendered on the server @@ -230,9 +223,9 @@ Component.prototype = componentProto = { }, getEl: function(key, index) { if (key) { - return this.___keyedElements[resolveKeyHelper(key, index)]; + return this.___keyedElements["@" + resolveKeyHelper(key, index)]; } else { - return this.___startNode; + return this.___rootNode && this.___rootNode.firstChild; } }, getEls: function(key) { @@ -248,29 +241,33 @@ Component.prototype = componentProto = { return els; }, getComponent: function(key, index) { - return componentLookup[resolveComponentIdHelper(this, key, index)]; + var rootNode = this.___keyedElements[resolveKeyHelper(key, index)]; + if (/\[\]$/.test(key)) { + // eslint-disable-next-line no-constant-condition + if ("MARKO_DEBUG") { + complain( + "A repeated key[] was passed to getComponent. Use a non-repeating key if there is only one of these components." + ); + } + rootNode = rootNode && rootNode[Object.keys(rootNode)[0]]; + } + return rootNode && rootNode.___markoComponent; }, getComponents: function(key) { - key = key + "[]"; - - var components = []; - var i = 0; - var component; - while ( - (component = - componentLookup[resolveComponentIdHelper(this, key, i)]) - ) { - components.push(component); - i++; - } - return components; + var lookup = this.___keyedElements[key + "[]"]; + return lookup + ? Object.keys(lookup).map(function(key) { + return lookup[key].___markoComponent; + }) + : []; }, destroy: function() { if (this.___destroyed) { return; } - var nodes = getNodes(this); + var root = this.___rootNode; + var nodes = this.___rootNode.nodes; this.___destroyShallow(); @@ -282,6 +279,8 @@ Component.prototype = componentProto = { } }); + root.detached = true; + delete componentLookup[this.id]; }, @@ -293,9 +292,9 @@ Component.prototype = componentProto = { emitLifecycleEvent(this, "destroy"); this.___destroyed = true; - this.___startNode.___markoComponent = undefined; + this.___rootNode.___markoComponent = undefined; - this.___startNode = this.___endNode = null; + this.___rootNode = null; // Unsubscribe from all DOM events this.___removeDOMEventListeners(); @@ -492,8 +491,7 @@ Component.prototype = componentProto = { throw TypeError(); } - var startNode = this.___startNode; - var endNodeNextSibling = this.___endNode.nextSibling; + var rootNode = this.___rootNode; var doc = self.___document; var input = this.___renderInput || this.___input; @@ -514,16 +512,9 @@ Component.prototype = componentProto = { var result = new RenderResult(out); - var targetNode = out.___getOutput(); + var targetNode = out.___getOutput().___firstChild; - morphdom( - startNode.parentNode, - startNode, - endNodeNextSibling, - targetNode, - doc, - componentsContext - ); + morphdom(rootNode, targetNode, doc, componentsContext); result.afterInsert(doc); }); @@ -532,23 +523,9 @@ Component.prototype = componentProto = { }, ___detach: function() { - var fragment = this.___document.createDocumentFragment(); - this.___forEachNode(fragment.appendChild.bind(fragment)); - return fragment; - }, - - ___forEachNode: function(callback) { - var currentNode = this.___startNode; - var endNode = this.___endNode; - - for (;;) { - var nextSibling = currentNode.nextSibling; - callback(currentNode); - if (currentNode == endNode) { - break; - } - currentNode = nextSibling; - } + var root = this.___rootNode; + root.remove(); + return root; }, ___removeDOMEventListeners: function() { @@ -589,12 +566,7 @@ Component.prototype = componentProto = { 'The "this.el" attribute is deprecated. Please use "this.getEl(key)" instead.' ); } - var el = this.___startNode; - while (el) { - if (el.nodeType === ELEMENT_NODE) return el; - if (el === this.___endNode) return; - el = el.nextSibling; - } + return this.___rootNode && this.___rootNode.firstChild; }, get els() { @@ -604,7 +576,9 @@ Component.prototype = componentProto = { 'The "this.els" attribute is deprecated. Please use "this.getEls(key)" instead.' ); } - return getNodes(this).filter(function(el) { + return (this.___rootNode ? this.___rootNode.nodes : []).filter(function( + el + ) { return el.nodeType === ELEMENT_NODE; }); } diff --git a/src/components/beginComponent-browser.js b/src/components/beginComponent-browser.js index 3a29ff03f..05a62f84e 100644 --- a/src/components/beginComponent-browser.js +++ b/src/components/beginComponent-browser.js @@ -1,6 +1,11 @@ var ComponentDef = require("./ComponentDef"); -module.exports = function beginComponent(componentsContext, component) { +module.exports = function beginComponent( + componentsContext, + component, + key, + ownerComponentDef +) { var componentId = component.id; var globalContext = componentsContext.___globalContext; @@ -13,6 +18,6 @@ module.exports = function beginComponent(componentsContext, component) { componentsContext.___components.push(componentDef); var out = componentsContext.___out; - out.bc(component); + out.bc(component, key, ownerComponentDef && ownerComponentDef.___component); return componentDef; }; diff --git a/src/components/beginComponent.js b/src/components/beginComponent.js index e42012817..9b0994e20 100644 --- a/src/components/beginComponent.js +++ b/src/components/beginComponent.js @@ -9,8 +9,9 @@ var FLAG_WILL_RERENDER_IN_BROWSER = 1; module.exports = function beginComponent( componentsContext, component, + key, + ownerComponentDef, isSplitComponent, - parentComponentDef, isImplicitComponent ) { var globalContext = componentsContext.___globalContext; @@ -25,8 +26,8 @@ module.exports = function beginComponent( // On the server if ( - parentComponentDef && - parentComponentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER + ownerComponentDef && + ownerComponentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER ) { componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER; return componentDef; @@ -47,9 +48,20 @@ module.exports = function beginComponent( if (isSplitComponent === false && out.global.noBrowserRerender !== true) { componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER; - out.w(""); + } + + if (ownerComponentDef && key != null) { + out.w( + "" + ); } else { - out.w(""); + out.w(""); } return componentDef; diff --git a/src/components/endComponent.js b/src/components/endComponent.js index f77e86970..779208a9e 100644 --- a/src/components/endComponent.js +++ b/src/components/endComponent.js @@ -2,6 +2,6 @@ module.exports = function endComponent(out, componentDef) { if (componentDef.___renderBoundary) { - out.w(""); + out.w(""); } }; diff --git a/src/components/init-components-browser.js b/src/components/init-components-browser.js index 8a2e1e574..3e0240066 100644 --- a/src/components/init-components-browser.js +++ b/src/components/init-components-browser.js @@ -3,36 +3,82 @@ var warp10Finalize = require("warp10/finalize"); var eventDelegation = require("./event-delegation"); var win = window; var defaultDocument = document; +var createFragmentNode = require("../morphdom/fragment").___createFragmentNode; var componentsUtil = require("./util"); var componentLookup = componentsUtil.___componentLookup; +var addComponentRootToKeyedElements = + componentsUtil.___addComponentRootToKeyedElements; var ComponentDef = require("./ComponentDef"); var registry = require("./registry"); var serverRenderedGlobals = {}; -var serverComponentStartNodes = {}; -var serverComponentEndNodes = {}; +var serverComponentRootNodes = {}; var keyedElementsByComponentId = {}; var FLAG_WILL_RERENDER_IN_BROWSER = 1; -var FLAG_HAS_BODY_EL = 2; -var FLAG_HAS_HEAD_EL = 4; -function indexServerComponentBoundaries(node) { +function indexServerComponentBoundaries(node, stack) { var componentId; + var ownerId; + var ownerComponent; + var keyedElements; + var nextSibling; + stack = stack || []; node = node.firstChild; while (node) { + nextSibling = node.nextSibling; if (node.nodeType === 8) { // Comment node var commentValue = node.nodeValue; if (commentValue[0] === "M") { - componentId = commentValue.substring(2); - var firstChar = commentValue[1]; - if (firstChar === "/") { - serverComponentEndNodes[componentId] = node; - } else if (firstChar === "^" || firstChar === "#") { - serverComponentStartNodes[componentId] = node; + if (firstChar === "^" || firstChar === "#") { + stack.push(node); + } else if (firstChar === "/") { + var endNode = node; + var startNode = stack.pop(); + var rootNode; + + if (startNode.parentNode === endNode.parentNode) { + rootNode = createFragmentNode( + startNode.nextSibling, + endNode + ); + } else { + rootNode = createFragmentNode( + endNode.parentNode.firstChild, + endNode + ); + } + + componentId = startNode.nodeValue.substring(2); + firstChar = startNode.nodeValue[1]; + + if (firstChar === "^") { + var parts = componentId.split(/ /g); + var key = parts[2]; + ownerId = parts[1]; + componentId = parts[0]; + if ((ownerComponent = componentLookup[ownerId])) { + keyedElements = ownerComponent.___keyedElements; + } else { + keyedElements = + keyedElementsByComponentId[ownerId] || + (keyedElementsByComponentId[ownerId] = {}); + } + addComponentRootToKeyedElements( + keyedElements, + key, + rootNode, + componentId + ); + } + + serverComponentRootNodes[componentId] = rootNode; + + startNode.parentNode.removeChild(startNode); + endNode.parentNode.removeChild(endNode); } } } else if (node.nodeType === 1) { @@ -41,11 +87,15 @@ function indexServerComponentBoundaries(node) { var markoProps = node.getAttribute("data-marko"); if (markoKey) { var separatorIndex = markoKey.indexOf(" "); - componentId = markoKey.substring(separatorIndex + 1); + ownerId = markoKey.substring(separatorIndex + 1); markoKey = markoKey.substring(0, separatorIndex); - var keyedElements = - keyedElementsByComponentId[componentId] || - (keyedElementsByComponentId[componentId] = {}); + if ((ownerComponent = componentLookup[ownerId])) { + keyedElements = ownerComponent.___keyedElements; + } else { + keyedElements = + keyedElementsByComponentId[ownerId] || + (keyedElementsByComponentId[ownerId] = {}); + } keyedElements[markoKey] = node; } if (markoProps) { @@ -58,10 +108,10 @@ function indexServerComponentBoundaries(node) { } }); } - indexServerComponentBoundaries(node); + indexServerComponentBoundaries(node, stack); } - node = node.nextSibling; + node = nextSibling; } } @@ -238,68 +288,39 @@ function initServerRendered(renderedComponents, doc) { serverRenderedGlobals, registry ); - var componentId = componentDef.id; - var component = componentDef.___component; - var startNode; - var endNode; - var flags = componentDef.___flags; - if ((flags & 6) === 6) { - startNode = document.head; - endNode = document.body; - } else if (flags & FLAG_HAS_BODY_EL) { - startNode = endNode = document.body; - } else if (flags & FLAG_HAS_HEAD_EL) { - startNode = endNode = document.head; - } else { - var startNodeComment = serverComponentStartNodes[componentId]; - var endNodeComment = serverComponentEndNodes[componentId]; - - startNode = startNodeComment.nextSibling; - - if (startNode === endNodeComment) { - // Component has no output nodes so just mount to the start comment node - // and we will remove the end comment node - startNode = endNode = startNodeComment; - } else { - delete serverComponentStartNodes[componentId]; - startNodeComment.parentNode.removeChild(startNodeComment); - - if (startNode.parentNode === document) { - endNode = startNode = document.documentElement; - } else { - // Remove the start and end comment nodes and use the inner nodes - // as the boundary - endNode = endNodeComment.previousSibling; + if (!hydrateComponent(componentDef, doc)) { + // hydrateComponent will return false if there is not rootNode + // for the component. If this is the case, we'll wait until the + // DOM has fully loaded to attempt to init the component again. + doc.addEventListener("DOMContentLoaded", function() { + if (!hydrateComponent(componentDef, doc)) { + indexServerComponentBoundaries(doc); + hydrateComponent(componentDef, doc); } - } - - if (endNodeComment) { - delete serverComponentEndNodes[componentId]; - endNodeComment.parentNode.removeChild(endNodeComment); - } + }); } + }); +} +function hydrateComponent(componentDef, doc) { + var componentId = componentDef.id; + var component = componentDef.___component; + var rootNode = serverComponentRootNodes[componentId]; + + if (rootNode) { + delete serverComponentRootNodes[componentId]; + + component.___rootNode = rootNode; + rootNode.___markoComponent = component; component.___keyedElements = keyedElementsByComponentId[componentId] || {}; - component.___startNode = startNode; - component.___endNode = endNode; - - startNode.___markoComponent = component; delete keyedElementsByComponentId[componentId]; - // Mark the start node so that we know we need to skip past this - // node when matching up children - startNode.___startNode = true; - - // Mark the end node so that when we attempt to find boundaries - // for nested UI components we don't accidentally go outside the boundary - // of the parent component - endNode.___endNode = true; - initComponent(componentDef, doc || defaultDocument); - }); + return true; + } } exports.___initClientRendered = initClientRendered; diff --git a/src/components/legacy/renderer-legacy.js b/src/components/legacy/renderer-legacy.js index 2c5f5969f..9440f50bc 100644 --- a/src/components/legacy/renderer-legacy.js +++ b/src/components/legacy/renderer-legacy.js @@ -50,34 +50,24 @@ function createRendererFunc(templateRenderFunc, componentProps) { var isRerender = component !== undefined; var id = assignedId; var isExisting; - var customEvents; - var scope; var parentComponentDef; + var ownerComponentDef = out.___assignedComponentDef; + var ownerComponentId = ownerComponentDef && ownerComponentDef.id; + var key = out.___assignedKey; + var customEvents = out.___assignedCustomEvents; if (component) { id = component.id; isExisting = true; globalComponentsContext.___rerenderComponent = null; } else { - parentComponentDef = componentsContext.___componentDef; - var componentDefFromArgs; - if ((componentDefFromArgs = out.___assignedComponentDef)) { - scope = componentDefFromArgs.id; + if ((parentComponentDef = componentsContext.___componentDef)) { out.___assignedComponentDef = null; - customEvents = out.___assignedCustomEvents; - var key = out.___assignedKey; - if (key != null) { key = key.toString(); } - id = - id || - resolveComponentKey( - globalComponentsContext, - key, - componentDefFromArgs - ); + id = id || resolveComponentKey(key, parentComponentDef); } else if (parentComponentDef) { id = parentComponentDef.___nextComponentId(); } else { @@ -94,7 +84,7 @@ function createRendererFunc(templateRenderFunc, componentProps) { out, typeName, customEvents, - scope + ownerComponentId ); } else { if (!component) { @@ -185,8 +175,9 @@ function createRendererFunc(templateRenderFunc, componentProps) { var componentDef = beginComponent( componentsContext, component, - isSplit, - parentComponentDef + key, + ownerComponentDef, + isSplit ); // This is a hack, but we have to swap out the component instance stored with this node @@ -227,11 +218,11 @@ function createRendererFunc(templateRenderFunc, componentProps) { if (customEvents && componentDef.___component) { if (registry.___isServer) { componentDef.___customEvents = customEvents; - componentDef.___scope = scope; + componentDef.___scope = ownerComponentId; } else { componentDef.___component.___setCustomEvents( customEvents, - scope + ownerComponentId ); } } diff --git a/src/components/renderer.js b/src/components/renderer.js index 811134a21..4adc36dd2 100644 --- a/src/components/renderer.js +++ b/src/components/renderer.js @@ -12,17 +12,11 @@ var endComponent = require("./endComponent"); var COMPONENT_BEGIN_ASYNC_ADDED_KEY = "$wa"; -function resolveComponentKey( - globalComponentsContext, - key, - ownerComponentDef, - parentComponentDef -) { +function resolveComponentKey(key, parentComponentDef) { if (key[0] === "#") { return key.substring(1); } else { - parentComponentDef = parentComponentDef || ownerComponentDef; - return parentComponentDef.___nextKey(ownerComponentDef.id + "-" + key); + return parentComponentDef.id + "-" + parentComponentDef.___nextKey(key); } } @@ -77,9 +71,10 @@ function createRendererFunc( var id; var isExisting; var customEvents; - var scope; var parentComponentDef = componentsContext.___componentDef; var ownerComponentDef = out.___assignedComponentDef; + var ownerComponentId = ownerComponentDef && ownerComponentDef.id; + var key = out.___assignedKey; if (component) { // If component is provided then we are currently rendering @@ -93,23 +88,17 @@ function createRendererFunc( // DOM (if any) so we will need to resolve the component ID from // the assigned key. We also need to handle any custom event bindings // that were provided. - if (ownerComponentDef) { + if (parentComponentDef) { // console.log('componentArgs:', componentArgs); - scope = ownerComponentDef.id; - out.___assignedComponentDef = null; - customEvents = out.___assignedCustomEvents; - var key = out.___assignedKey; if (key != null) { id = resolveComponentKey( - globalComponentsContext, key.toString(), - ownerComponentDef, parentComponentDef ); } else { - id = ownerComponentDef.___nextComponentId(); + id = parentComponentDef.___nextComponentId(); } } else { id = globalComponentsContext.___nextComponentId(); @@ -128,7 +117,7 @@ function createRendererFunc( out, typeName, customEvents, - scope + ownerComponentId ); // This is the final input after running the lifecycle methods. @@ -176,7 +165,10 @@ function createRendererFunc( component.___updateQueued = true; if (customEvents !== undefined) { - component.___setCustomEvents(customEvents, scope); + component.___setCustomEvents( + customEvents, + ownerComponentId + ); } if (isExisting === false) { @@ -212,8 +204,9 @@ function createRendererFunc( var componentDef = beginComponent( componentsContext, component, - isSplit, + key, ownerComponentDef, + isSplit, isImplicitComponent ); diff --git a/src/components/taglib/TransformHelper/assignComponentId.js b/src/components/taglib/TransformHelper/assignComponentId.js index e5f22fa5e..223ed936e 100644 --- a/src/components/taglib/TransformHelper/assignComponentId.js +++ b/src/components/taglib/TransformHelper/assignComponentId.js @@ -88,7 +88,6 @@ module.exports = function assignComponentId(isRepeated) { true /* user assigned key */ ); } else { - idExpression = assignedKey; if (el.data.userAssignedKey !== false) { if ( context.data.hasLegacyForKey || @@ -101,7 +100,18 @@ module.exports = function assignComponentId(isRepeated) { } } - el.setKey(assignedKey); + // mark as user-assigned key (@) + if (assignedKey.type === "Literal") { + idExpression = builder.literal("@" + assignedKey.value); + } else { + idExpression = builder.binaryExpression( + builder.literal("@"), + "+", + assignedKey + ); + } + + el.setKey(idExpression); this.serializeKey(); } } else { @@ -109,6 +119,8 @@ module.exports = function assignComponentId(isRepeated) { let parentForKey = getParentForKeyVar(el, this); let uniqueKey = this.nextUniqueId(); + el.isAutoKeyed = true; + nestedIdExpression = isRepeated ? builder.literal(uniqueKey + "[]") : builder.literal(uniqueKey.toString()); diff --git a/src/components/taglib/TransformHelper/convertToComponent.js b/src/components/taglib/TransformHelper/convertToComponent.js index 1888779db..5475b9a20 100644 --- a/src/components/taglib/TransformHelper/convertToComponent.js +++ b/src/components/taglib/TransformHelper/convertToComponent.js @@ -1,10 +1,6 @@ "use strict"; const generateRegisterComponentCode = require("../util/generateRegisterComponentCode"); -// var FLAG_WILL_RERENDER_IN_BROWSER = 1; -var FLAG_HAS_BODY_EL = 2; -var FLAG_HAS_HEAD_EL = 4; - module.exports = function handleComponentBind(options) { let context = this.context; let builder = this.builder; @@ -87,31 +83,6 @@ module.exports = function handleComponentBind(options) { return; } - var flags = 0; - - rootNodes.forEach(rootNode => { - if (rootNode.type === "HtmlElement") { - if (rootNode.tagName === "body") { - flags |= FLAG_HAS_BODY_EL; - } else if (rootNode.tagName === "head") { - flags |= FLAG_HAS_HEAD_EL; - } - } - }); - - if (flags) { - context.root.appendChild( - builder.assignment( - builder.memberExpression( - builder.identifier("__component"), - builder.identifier("___flags") - ), - builder.literal(flags), - "|=" - ) - ); - } - let markoComponentVar; if (rendererModule) { diff --git a/src/components/util-browser.js b/src/components/util-browser.js index 7cb5059f5..a53fd6057 100644 --- a/src/components/util-browser.js +++ b/src/components/util-browser.js @@ -6,6 +6,14 @@ var componentLookup = {}; var defaultDocument = document; var EMPTY_OBJECT = {}; +function getParentComponentForEl(node) { + while (node && !node.___markoComponent) { + node = node.previousSibling || node.parentNode; + node = (node && node.fragment) || node; + } + return node && node.___markoComponent; +} + function getComponentForEl(el, doc) { if (el) { var node = @@ -13,7 +21,7 @@ function getComponentForEl(el, doc) { ? (doc || defaultDocument).getElementById(el) : el; if (node) { - return node.___markoComponent; + return getParentComponentForEl(node); } } } @@ -50,7 +58,7 @@ function emitLifecycleEvent(component, eventType, eventArg1, eventArg2) { } function destroyComponentForNode(node) { - var componentToDestroy = node.___markoComponent; + var componentToDestroy = (node.fragment || node).___markoComponent; if (componentToDestroy) { componentToDestroy.___destroyShallow(); delete componentLookup[componentToDestroy.id]; @@ -58,17 +66,23 @@ function destroyComponentForNode(node) { } function destroyNodeRecursive(node, component) { destroyComponentForNode(node); - if (node.nodeType === 1) { + if (node.nodeType === 1 || node.nodeType === 12) { var key; if (component && (key = node.___markoKey)) { if (node === component.___keyedElements[key]) { - delete component.___keyedElements[key]; + if (node.___markoComponent && /\[\]$/.test(key)) { + delete component.___keyedElements[key][ + node.___markoComponent.id + ]; + } else { + delete component.___keyedElements[key]; + } } } var curChild = node.firstChild; - while (curChild) { + while (curChild && curChild !== node.endNode) { destroyNodeRecursive(curChild, component); curChild = curChild.nextSibling; } @@ -122,6 +136,28 @@ function getMarkoPropsFromEl(el) { return virtualProps; } +function normalizeComponentKey(key, parentId) { + if (key[0] === "#") { + key = key.replace("#" + parentId + "-", ""); + } + return key; +} + +function addComponentRootToKeyedElements( + keyedElements, + key, + rootNode, + componentId +) { + if (/\[\]$/.test(key)) { + var repeatedElementsForKey = (keyedElements[key] = + keyedElements[key] || {}); + repeatedElementsForKey[componentId] = rootNode; + } else { + keyedElements[key] = rootNode; + } +} + exports.___runtimeId = runtimeId; exports.___componentLookup = componentLookup; exports.___getComponentForEl = getComponentForEl; @@ -131,3 +167,5 @@ exports.___destroyNodeRecursive = destroyNodeRecursive; exports.___nextComponentIdProvider = nextComponentIdProvider; exports.___attachBubblingEvent = attachBubblingEvent; exports.___getMarkoPropsFromEl = getMarkoPropsFromEl; +exports.___addComponentRootToKeyedElements = addComponentRootToKeyedElements; +exports.___normalizeComponentKey = normalizeComponentKey; diff --git a/src/morphdom/fragment.js b/src/morphdom/fragment.js new file mode 100644 index 000000000..c94bd2d5b --- /dev/null +++ b/src/morphdom/fragment.js @@ -0,0 +1,81 @@ +var helpers = require("./helpers"); +var insertBefore = helpers.___insertBefore; + +var fragmentPrototype = { + nodeType: 12, + get firstChild() { + let firstChild = this.startNode.nextSibling; + return firstChild === this.endNode ? undefined : firstChild; + }, + get lastChild() { + let lastChild = this.endNode.previousSibling; + return lastChild === this.startNode ? undefined : lastChild; + }, + get parentNode() { + let parentNode = this.startNode.parentNode; + return parentNode === this.detachedContainer ? undefined : parentNode; + }, + get nextSibling() { + return this.endNode.nextSibling; + }, + get nodes() { + const nodes = []; + let current = this.startNode; + while (current !== this.endNode) { + nodes.push(current); + current = current.nextSibling; + } + nodes.push(current); + return nodes; + }, + insertBefore(newChildNode, referenceNode) { + const actualReference = + referenceNode == null ? this.endNode : referenceNode; + return insertBefore( + newChildNode, + actualReference, + this.startNode.parentNode + ); + }, + insertInto(newParentNode, referenceNode) { + this.nodes.forEach(function(node) { + insertBefore(node, referenceNode, newParentNode); + }); + return this; + }, + remove() { + this.nodes.forEach(function(node) { + this.detachedContainer.appendChild(node); + }); + } +}; + +function createFragmentNode(startNode, nextNode, parentNode) { + var fragment = Object.create(fragmentPrototype); + fragment.startNode = document.createTextNode(""); + fragment.endNode = document.createTextNode(""); + fragment.startNode.fragment = fragment; + fragment.endNode.fragment = fragment; + var detachedContainer = (fragment.detachedContainer = document.createDocumentFragment()); + parentNode = + parentNode || (startNode && startNode.parentNode) || detachedContainer; + insertBefore(fragment.startNode, startNode, parentNode); + insertBefore(fragment.endNode, nextNode, parentNode); + return fragment; +} + +function beginFragmentNode(startNode, parentNode) { + var fragment = createFragmentNode(startNode, null, parentNode); + fragment.___finishFragment = function(nextNode) { + fragment.___finishFragment = null; + insertBefore( + fragment.endNode, + nextNode, + parentNode || startNode.parentNode + ); + }; + return fragment; +} + +exports.___createFragmentNode = createFragmentNode; +exports.___beginFragmentNode = beginFragmentNode; diff --git a/src/morphdom/helpers.js b/src/morphdom/helpers.js new file mode 100644 index 000000000..e44b71215 --- /dev/null +++ b/src/morphdom/helpers.js @@ -0,0 +1,42 @@ +function insertBefore(node, referenceNode, parentNode) { + if (node.insertInto) { + return node.insertInto(parentNode, referenceNode); + } + return parentNode.insertBefore( + node, + (referenceNode && referenceNode.startNode) || referenceNode + ); +} + +function insertAfter(node, referenceNode, parentNode) { + return insertBefore( + node, + referenceNode && referenceNode.nextSibling, + parentNode + ); +} + +function nextSibling(node) { + var next = node.nextSibling; + var fragment = next && next.fragment; + if (fragment) { + return next === fragment.startNode ? fragment : null; + } + return next; +} + +function firstChild(node) { + var next = node.firstChild; + return (next && next.fragment) || next; +} + +function removeChild(node) { + if (node.remove) node.remove(); + else node.parentNode.removeChild(node); +} + +exports.___insertBefore = insertBefore; +exports.___insertAfter = insertAfter; +exports.___nextSibling = nextSibling; +exports.___firstChild = firstChild; +exports.___removeChild = removeChild; diff --git a/src/morphdom/index.js b/src/morphdom/index.js index 683b17ead..718e42a2b 100644 --- a/src/morphdom/index.js +++ b/src/morphdom/index.js @@ -3,15 +3,29 @@ var specialElHandlers = require("./specialElHandlers"); var componentsUtil = require("../components/util"); var existingComponentLookup = componentsUtil.___componentLookup; var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive; +var addComponentRootToKeyedElements = + componentsUtil.___addComponentRootToKeyedElements; +var normalizeComponentKey = componentsUtil.___normalizeComponentKey; var VElement = require("../runtime/vdom/vdom").___VElement; var virtualizeElement = VElement.___virtualize; var morphAttrs = VElement.___morphAttrs; var eventDelegation = require("../components/event-delegation"); +var fragment = require("./fragment"); +var helpers = require("./helpers"); + +var insertBefore = helpers.___insertBefore; +var insertAfter = helpers.___insertAfter; +var nextSibling = helpers.___nextSibling; +var firstChild = helpers.___firstChild; +var removeChild = helpers.___removeChild; +var createFragmentNode = fragment.___createFragmentNode; +var beginFragmentNode = fragment.___beginFragmentNode; var ELEMENT_NODE = 1; var TEXT_NODE = 3; var COMMENT_NODE = 8; var COMPONENT_NODE = 2; +var FRAGMENT_NODE = 12; // var FLAG_IS_SVG = 1; // var FLAG_IS_TEXTAREA = 2; @@ -19,6 +33,10 @@ var COMPONENT_NODE = 2; var FLAG_PRESERVE = 8; // var FLAG_CUSTOM_ELEMENT = 16; +function isAutoKey(key) { + return !/^@/.test(key); +} + function compareNodeNames(fromEl, toEl) { return fromEl.___nodeName === toEl.___nodeName; } @@ -29,54 +47,40 @@ function onNodeAdded(node, componentsContext) { } } -function insertBefore(node, referenceNode, parentNode) { - return parentNode.insertBefore(node, referenceNode); -} - -function insertAfter(node, referenceNode, parentNode) { - return parentNode.insertBefore( - node, - referenceNode && referenceNode.nextSibling - ); -} - -function morphdom( - parentNode, - startNode, - endNode, - toNode, - doc, - componentsContext -) { +function morphdom(fromNode, toNode, doc, componentsContext) { var globalComponentsContext; var isRerenderInBrowser = false; + var keySequences = {}; if (componentsContext) { globalComponentsContext = componentsContext.___globalContext; isRerenderInBrowser = globalComponentsContext.___isRerenderInBrowser; } - function createMarkerComment() { - return doc.createComment("$marko"); - } - function insertVirtualNodeBefore( vNode, key, referenceEl, parentEl, - component + ownerComponent, + parentComponent ) { var realNode = vNode.___actualize(doc); insertBefore(realNode, referenceEl, parentEl); - if (vNode.___nodeType === ELEMENT_NODE) { + if ( + vNode.___nodeType === ELEMENT_NODE || + vNode.___nodeType === FRAGMENT_NODE + ) { if (key) { realNode.___markoKey = key; - component.___keyedElements[key] = realNode; + (isAutoKey(key) + ? parentComponent + : ownerComponent + ).___keyedElements[key] = realNode; } - morphChildren(realNode, null, null, vNode, component); + morphChildren(realNode, vNode, parentComponent); } onNodeAdded(realNode, componentsContext); @@ -86,144 +90,45 @@ function morphdom( vComponent, referenceNode, referenceNodeParentEl, - component + component, + key, + ownerComponent, + parentComponent ) { - component.___startNode = component.___endNode = insertBefore( - createMarkerComment(), + var rootNode = (component.___rootNode = insertBefore( + createFragmentNode(), referenceNode, referenceNodeParentEl - ); - morphComponent(referenceNodeParentEl, component, vComponent); + )); + rootNode.___markoComponent = component; + + if (key && ownerComponent) { + key = normalizeComponentKey(key, parentComponent.id); + addComponentRootToKeyedElements( + ownerComponent.___keyedElements, + key, + rootNode, + component.id + ); + rootNode.___markoKey = key; + } + + morphComponent(component, vComponent); } - function resolveComponentEndNode(startNode, vChild, parentNode) { - var endNode = startNode; - - // We track text nodes because multiple adjacent VText nodes should - // be treated as a single VText node for purposes of pairing with HTML - // that was rendered on the server since browsers will only see - // a single text node - var isPrevText = vChild.___nodeType === TEXT_NODE; - - while ((vChild = vChild.___nextSibling)) { - var nextRealNode = endNode.nextSibling; - - // We stop when there are no more corresponding real nodes or when - // we reach the end boundary for our UI component - if (!nextRealNode || nextRealNode.___endNode) { - break; - } - var isText = vChild.___nodeType === TEXT_NODE; - if (isText && isPrevText) { - // Pretend like we didn't see this VText node since it - // the previous vnode was also a VText node - continue; - } - endNode = nextRealNode; - isPrevText = isText; - } - - if (endNode === startNode) { - return insertAfter(createMarkerComment(), startNode, parentNode); - } - - return endNode; - } - - function morphComponent(parentFromNode, component, vComponent) { - // We create a key sequence to generate unique keys since a key - // can be repeated - component.___keySequence = globalComponentsContext.___createKeySequence(); - - var startNode = component.___startNode; - var endNode = component.___endNode; - startNode.___markoComponent = undefined; - endNode.___endNode = undefined; - - var beforeChild = startNode.previousSibling; - var afterChild = endNode.nextSibling; - var tempChild; - - if (!beforeChild) { - tempChild = beforeChild = insertBefore( - createMarkerComment(), - startNode, - parentFromNode - ); - } - - morphChildren( - parentFromNode, - startNode, - afterChild, - vComponent, - component - ); - - endNode = undefined; - - startNode = beforeChild.nextSibling; - if (!startNode || startNode === afterChild) { - startNode = endNode = insertAfter( - createMarkerComment(), - beforeChild, - parentFromNode - ); - } - - if (tempChild) { - parentFromNode.removeChild(tempChild); - } - - if (!endNode) { - if (afterChild) { - endNode = afterChild.previousSibling; - } else { - endNode = parentFromNode.lastChild; - } - } - - // Make sure we don't use a detached node as the component boundary and - // we can't use a node that is already the boundary node for another component - if ( - startNode.___markoDetached !== undefined || - startNode.___markoComponent - ) { - startNode = insertBefore( - createMarkerComment(), - startNode, - parentFromNode - ); - } - - if (endNode.___markoDetached !== undefined || endNode.___endNode) { - endNode = insertAfter( - createMarkerComment(), - endNode, - parentFromNode - ); - } - - startNode.___markoComponent = component; - endNode.___endNode = true; - - component.___startNode = startNode; - component.___endNode = endNode; - - component.___keySequence = undefined; // We don't need to track keys anymore - - return afterChild; + function morphComponent(component, vComponent) { + morphChildren(component.___rootNode, vComponent, component); } var detachedNodes = []; - function detachNode(node, parentNode, component) { - if (node.nodeType === ELEMENT_NODE) { + function detachNode(node, parentNode, ownerComponent) { + if (node.nodeType === ELEMENT_NODE || node.nodeType === FRAGMENT_NODE) { detachedNodes.push(node); - node.___markoDetached = component || true; + node.___markoDetached = ownerComponent || true; } else { destroyNodeRecursive(node); - parentNode.removeChild(node); + removeChild(node); } } @@ -231,14 +136,8 @@ function morphdom( component.destroy(); } - function morphChildren( - parentFromNode, - startNode, - endNode, - toNode, - component - ) { - var curFromNodeChild = startNode; + function morphChildren(fromNode, toNode, parentComponent) { + var curFromNodeChild = firstChild(fromNode); var curToNodeChild = toNode.___firstChild; var curToNodeKey; @@ -255,56 +154,57 @@ function morphdom( outer: while (curToNodeChild) { toNextSibling = curToNodeChild.___nextSibling; curToNodeType = curToNodeChild.___nodeType; + curToNodeKey = curToNodeChild.___key; - var componentForNode = curToNodeChild.___component || component; + var ownerComponent = + curToNodeChild.___ownerComponent || parentComponent; + var referenceComponent; if (curToNodeType === COMPONENT_NODE) { + var component = curToNodeChild.___component; if ( (matchingFromComponent = - existingComponentLookup[componentForNode.id]) === - undefined + existingComponentLookup[component.id]) === undefined ) { if (isRerenderInBrowser === true) { - var firstVChild = curToNodeChild.___firstChild; - if (firstVChild) { - if (!curFromNodeChild) { - curFromNodeChild = insertBefore( - createMarkerComment(), - null, - parentFromNode - ); - } + var rootNode = beginFragmentNode( + curFromNodeChild, + fromNode + ); + component.___rootNode = rootNode; + rootNode.___markoComponent = component; - componentForNode.___startNode = curFromNodeChild; - componentForNode.___endNode = resolveComponentEndNode( - curFromNodeChild, - firstVChild, - parentFromNode + if (ownerComponent && curToNodeKey) { + curToNodeKey = normalizeComponentKey( + curToNodeKey, + parentComponent.id ); - } else { - componentForNode.___startNode = componentForNode.___endNode = insertBefore( - createMarkerComment(), - curFromNodeChild, - parentFromNode + addComponentRootToKeyedElements( + ownerComponent.___keyedElements, + curToNodeKey, + rootNode, + component.id ); + rootNode.___markoKey = curToNodeKey; } - curFromNodeChild = morphComponent( - parentFromNode, - componentForNode, - curToNodeChild - ); + morphComponent(component, curToNodeChild); + + curFromNodeChild = nextSibling(rootNode); } else { insertVirtualComponentBefore( curToNodeChild, curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + component, + curToNodeKey, + ownerComponent, + parentComponent ); } } else { if ( - matchingFromComponent.___startNode !== curFromNodeChild + matchingFromComponent.___rootNode !== curFromNodeChild ) { if ( curFromNodeChild && @@ -316,66 +216,67 @@ function morphdom( ) { // The component associated with the current real DOM node was not rendered // so we should just remove it out of the real DOM by destroying it - curFromNodeChild = - fromComponent.___endNode.nextSibling; + curFromNodeChild = nextSibling( + fromComponent.___rootNode + ); destroyComponent(fromComponent); continue; } // We need to move the existing component into - // the correct location and preserve focus. - var activeElement = doc.activeElement; + // the correct location insertBefore( - matchingFromComponent.___detach(), + matchingFromComponent.___rootNode, curFromNodeChild, - parentFromNode + fromNode ); - // This focus patch should be a temporary fix. - if ( - activeElement !== doc.activeElement && - activeElement.focus - ) { - activeElement.focus(); - } + } else { + curFromNodeChild = + curFromNodeChild && nextSibling(curFromNodeChild); } - if (curToNodeChild.___preserve) { - curFromNodeChild = - matchingFromComponent.___endNode.nextSibling; - } else { - curFromNodeChild = morphComponent( - parentFromNode, - componentForNode, - curToNodeChild - ); + if (!curToNodeChild.___preserve) { + morphComponent(component, curToNodeChild); } } curToNodeChild = toNextSibling; continue; - } else if ((curToNodeKey = curToNodeChild.___key)) { + } else if (curToNodeKey) { curVFromNodeChild = undefined; curFromNodeKey = undefined; + if (isAutoKey(curToNodeKey)) { + if (ownerComponent !== parentComponent) { + curToNodeKey += ":" + ownerComponent.id; + } + referenceComponent = parentComponent; + } else { + referenceComponent = ownerComponent; + } + var keySequence = - componentForNode.___keySequence || - (componentForNode.___keySequence = globalComponentsContext.___createKeySequence()); + keySequences[referenceComponent.id] || + (keySequences[ + referenceComponent.id + ] = globalComponentsContext.___createKeySequence()); // We have a keyed element. This is the fast path for matching // up elements curToNodeKey = keySequence.___nextKey(curToNodeKey); if (curFromNodeChild) { - if (curFromNodeChild !== endNode) { - curFromNodeKey = curFromNodeChild.___markoKey; - curVFromNodeChild = curFromNodeChild.___markoVElement; - fromNextSibling = curFromNodeChild.nextSibling; - } + curFromNodeKey = curFromNodeChild.___markoKey; + curVFromNodeChild = curFromNodeChild.___markoVElement; + fromNextSibling = nextSibling(curFromNodeChild); } if (curFromNodeKey === curToNodeKey) { // Elements line up. Now we just have to make sure they are compatible - if ((curToNodeChild.___flags & FLAG_PRESERVE) === 0) { + if ( + (curToNodeChild.___flags & FLAG_PRESERVE) === 0 && + !curToNodeChild.___preserve + ) { // We just skip over the fromNode if it is preserved if ( @@ -385,15 +286,16 @@ function morphdom( curFromNodeChild, curVFromNodeChild, curToNodeChild, - componentForNode, - curToNodeKey + curToNodeKey, + ownerComponent, + parentComponent ); } else { // Remove the old node detachNode( curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent ); // Incompatible nodes. Just move the target VNode into the DOM at this position @@ -401,8 +303,9 @@ function morphdom( curToNodeChild, curToNodeKey, curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent, + parentComponent ); } } else { @@ -411,38 +314,76 @@ function morphdom( } else { if ( (matchingFromEl = - componentForNode.___keyedElements[curToNodeKey]) === - undefined - ) { - if ( - isRerenderInBrowser === true && - curFromNodeChild && - curFromNodeChild.nodeType === ELEMENT_NODE && - curFromNodeChild.nodeName === - curToNodeChild.___nodeName - ) { - curVFromNodeChild = virtualizeElement( - curFromNodeChild - ); - curFromNodeChild.___markoKey = curToNodeKey; - morphEl( - curFromNodeChild, - curVFromNodeChild, - curToNodeChild, - componentForNode, + referenceComponent.___keyedElements[ curToNodeKey - ); - curToNodeChild = toNextSibling; - curFromNodeChild = fromNextSibling; - continue; + ]) === undefined + ) { + if (isRerenderInBrowser === true && curFromNodeChild) { + if ( + curFromNodeChild.nodeType === ELEMENT_NODE && + curFromNodeChild.nodeName === + curToNodeChild.___nodeName + ) { + curVFromNodeChild = virtualizeElement( + curFromNodeChild + ); + curFromNodeChild.___markoKey = curToNodeKey; + morphEl( + curFromNodeChild, + curVFromNodeChild, + curToNodeChild, + curToNodeKey, + ownerComponent, + parentComponent + ); + curToNodeChild = toNextSibling; + curFromNodeChild = fromNextSibling; + continue; + } else if ( + curToNodeChild.___nodeType === FRAGMENT_NODE && + curFromNodeChild.nodeType === COMMENT_NODE + ) { + var content = curFromNodeChild.nodeValue; + if (content == "F#" + curToNodeKey) { + var endNode = curFromNodeChild; + while ( + endNode.nodeType !== COMMENT_NODE || + endNode.nodeValue !== "F/" + ) + endNode = endNode.nextSibling; + + var fragment = createFragmentNode( + curFromNodeChild, + endNode.nextSibling, + fromNode + ); + fragment.___markoKey = curToNodeKey; + fragment.___markoVElement = curToNodeChild; + removeChild(curFromNodeChild); + removeChild(endNode); + + if (!curToNodeChild.___preserve) { + morphChildren( + fragment, + curToNodeChild, + parentComponent + ); + } + + curToNodeChild = toNextSibling; + curFromNodeChild = fragment.nextSibling; + continue; + } + } } insertVirtualNodeBefore( curToNodeChild, curToNodeKey, curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent, + parentComponent ); fromNextSibling = curFromNodeChild; } else { @@ -479,7 +420,7 @@ function morphdom( insertBefore( matchingFromEl, curFromNodeChild, - parentFromNode + fromNode ); } else { // Single element removal @@ -488,14 +429,15 @@ function morphdom( // and the matching real DOM node will fall into // place. We will continue diffing with next sibling // after the real DOM node that just fell into place - fromNextSibling = - fromNextSibling.nextSibling; + fromNextSibling = nextSibling( + fromNextSibling + ); if (curFromNodeChild) { detachNode( curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent ); } } @@ -509,14 +451,14 @@ function morphdom( insertAfter( matchingFromEl, curFromNodeChild, - parentFromNode + fromNode ); if (curFromNodeChild) { detachNode( curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent ); } } @@ -529,9 +471,9 @@ function morphdom( matchingFromEl, curVFromNodeChild, curToNodeChild, - componentForNode, curToNodeKey, - curToNodeKey + ownerComponent, + parentComponent ); } } else { @@ -539,13 +481,14 @@ function morphdom( curToNodeChild, curToNodeKey, curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent, + parentComponent ); detachNode( matchingFromEl, - parentFromNode, - componentForNode + fromNode, + ownerComponent ); } } @@ -559,18 +502,17 @@ function morphdom( // The know the target node is not a VComponent node and we know // it is also not a preserve node. Let's now match up the HTML // element, text node, comment, etc. - while (curFromNodeChild && curFromNodeChild !== endNode) { - if ( - (fromComponent = curFromNodeChild.___markoComponent) && - fromComponent !== componentForNode - ) { + while (curFromNodeChild) { + fromNextSibling = nextSibling(curFromNodeChild); + + if ((fromComponent = curFromNodeChild.___markoComponent)) { // The current "to" element is not associated with a component, // but the current "from" element is associated with a component // Even if we destroy the current component in the original // DOM or not, we still need to skip over it since it is // not compatible with the current "to" node - curFromNodeChild = fromComponent.___endNode.nextSibling; + curFromNodeChild = fromNextSibling; if ( !globalComponentsContext.___renderedComponentsById[ @@ -583,8 +525,6 @@ function morphdom( continue; // Move to the next "from" node } - fromNextSibling = curFromNodeChild.nextSibling; - var curFromNodeType = curFromNodeChild.nodeType; var isCompatible = undefined; @@ -626,8 +566,9 @@ function morphdom( curFromNodeChild, curVFromNodeChild, curToNodeChild, - component, - curToNodeKey + curToNodeKey, + ownerComponent, + parentComponent ); } } else if ( @@ -638,44 +579,10 @@ function morphdom( isCompatible = true; // Simply update nodeValue on the original node to // change the text value - - var content = curFromNodeChild.nodeValue; - if (content == curToNodeChild.___nodeValue) { - if (/^F\^/.test(content)) { - var closingContent = content.replace( - /^F\^/, - "F/" - ); - while ( - (curFromNodeChild = - curFromNodeChild.nextSibling) - ) { - if ( - curFromNodeChild.nodeValue === - closingContent - ) { - break; - } - } - while ( - (curToNodeChild = - curToNodeChild.___nextSibling) - ) { - if ( - curToNodeChild.___nodeValue === - closingContent - ) { - break; - } - } - curToNodeChild = curToNodeChild.___nextSibling; - curFromNodeChild = - curFromNodeChild === endNode - ? null - : curFromNodeChild.nextSibling; - continue outer; - } - } else { + if ( + curFromNodeChild.nodeValue !== + curToNodeChild.___nodeValue + ) { curFromNodeChild.nodeValue = curToNodeChild.___nodeValue; } @@ -695,18 +602,10 @@ function morphdom( curFromNodeKey ] === undefined ) { - detachNode( - curFromNodeChild, - parentFromNode, - componentForNode - ); + detachNode(curFromNodeChild, fromNode, ownerComponent); } } else { - detachNode( - curFromNodeChild, - parentFromNode, - componentForNode - ); + detachNode(curFromNodeChild, fromNode, ownerComponent); } curFromNodeChild = fromNextSibling; @@ -720,54 +619,69 @@ function morphdom( curToNodeChild, curToNodeKey, curFromNodeChild, - parentFromNode, - componentForNode + fromNode, + ownerComponent, + parentComponent ); curToNodeChild = toNextSibling; curFromNodeChild = fromNextSibling; } - // We have processed all of the "to nodes". If curFromNodeChild is - // non-null then we still have some from nodes left over that need - // to be removed - while ( - curFromNodeChild && - (endNode === null || curFromNodeChild !== endNode) - ) { - fromNextSibling = curFromNodeChild.nextSibling; + // We have processed all of the "to nodes". + if (fromNode.___finishFragment) { + // If we are in an unfinished fragment, we have reached the end of the nodes + // we were matching up and need to end the fragment + fromNode.___finishFragment(curFromNodeChild); + } else { + // If curFromNodeChild is non-null then we still have some from nodes + // left over that need to be removed + while (curFromNodeChild) { + fromNextSibling = nextSibling(curFromNodeChild); - if ((fromComponent = curFromNodeChild.___markoComponent)) { - curFromNodeChild = fromComponent.___endNode.nextSibling; - if ( - !globalComponentsContext.___renderedComponentsById[ - fromComponent.id - ] - ) { - destroyComponent(fromComponent); + if ((fromComponent = curFromNodeChild.___markoComponent)) { + curFromNodeChild = fromNextSibling; + if ( + !globalComponentsContext.___renderedComponentsById[ + fromComponent.id + ] + ) { + destroyComponent(fromComponent); + } + continue; } - continue; + + curVFromNodeChild = curFromNodeChild.___markoVElement; + + // For transcluded content, we need to check if the element belongs to a different component + // context than the current component and ensure it gets removed from its key index. + if (isAutoKey(fromNode.___markoKey)) { + referenceComponent = parentComponent; + } else { + referenceComponent = + curVFromNodeChild && + curVFromNodeChild.___ownerComponent; + } + + detachNode(curFromNodeChild, fromNode, referenceComponent); + + curFromNodeChild = fromNextSibling; } - - curVFromNodeChild = curFromNodeChild.___markoVElement; - - // For transcluded content, we need to check if the element belongs to a different component - // context than the current component and ensure it gets removed from its key index. - fromComponent = - (curVFromNodeChild && curVFromNodeChild.___component) || - component; - - detachNode(curFromNodeChild, parentFromNode, fromComponent); - - curFromNodeChild = fromNextSibling; } } - function morphEl(fromEl, vFromEl, toEl, component, toElKey) { + function morphEl( + fromEl, + vFromEl, + toEl, + toElKey, + ownerComponent, + parentComponent + ) { var nodeName = toEl.___nodeName; if (isRerenderInBrowser === true && toElKey) { - component.___keyedElements[toElKey] = fromEl; + ownerComponent.___keyedElements[toElKey] = fromEl; } var constId = toEl.___constId; @@ -786,7 +700,7 @@ function morphdom( } if (nodeName !== "TEXTAREA") { - morphChildren(fromEl, fromEl.firstChild, null, toEl, component); + morphChildren(fromEl, toEl, parentComponent); } var specialElHandler = specialElHandlers[nodeName]; @@ -795,7 +709,7 @@ function morphdom( } } // END: morphEl(...) - morphChildren(parentNode, startNode, endNode, toNode); + morphChildren(fromNode, toNode, toNode.___component); detachedNodes.forEach(function(node) { var detachedFromComponent = node.___markoDetached; @@ -813,7 +727,7 @@ function morphdom( ); if (eventDelegation.___handleNodeDetach(node) != false) { - node.parentNode.removeChild(node); + removeChild(node); } } } diff --git a/src/morphdom/specialElHandlers.js b/src/morphdom/specialElHandlers.js index 0dea90513..6b385be0a 100644 --- a/src/morphdom/specialElHandlers.js +++ b/src/morphdom/specialElHandlers.js @@ -33,7 +33,7 @@ SpecialElHandlers.prototype = { fromEl.value = toEl.___value; } - if (!toEl.___hasAttribute("value")) { + if (fromEl.hasAttribute("value") && !toEl.___hasAttribute("value")) { fromEl.removeAttribute("value"); } }, @@ -63,18 +63,21 @@ SpecialElHandlers.prototype = { SELECT: function(fromEl, toEl) { if (!toEl.___hasAttribute("multiple")) { var i = -1; + var selected = 0; var curChild = toEl.___firstChild; while (curChild) { if (curChild.___nodeName == "OPTION") { i++; if (curChild.___hasAttribute("selected")) { - break; + selected = i; } } curChild = curChild.___nextSibling; } - fromEl.selectedIndex = i; + if (fromEl.selectedIndex !== selected) { + fromEl.selectedIndex = selected; + } } } }; diff --git a/src/runtime/helpers.js b/src/runtime/helpers.js index 744389a54..058b4f7d1 100644 --- a/src/runtime/helpers.js +++ b/src/runtime/helpers.js @@ -1,5 +1,8 @@ "use strict"; var removeDashes = require("../compiler/util/removeDashes"); +var ComponentsContext = require("../components/ComponentsContext"); +var getComponentsContext = ComponentsContext.___getComponentsContext; +var ComponentDef = require("../components/ComponentDef"); var isArray = Array.isArray; var RENDER_BODY_TOKEN = "%FN"; var RENDER_BODY_TO_JSON = function() { @@ -105,8 +108,8 @@ var helpers = { */ d: function dynamicTag(tag, attrs, out, componentDef, key, customEvents) { if (tag) { + var component = componentDef && componentDef.___component; if (typeof tag === "string") { - var component = componentDef && componentDef.component; var events = customEvents && customEvents.reduce(function(events, eventArray) { @@ -161,17 +164,28 @@ var helpers = { if (isFn || isToken) { var flags = componentDef ? componentDef.___flags : 0; - var parentId = componentDef ? componentDef.id : ""; var willRerender = flags & FLAG_WILL_RERENDER_IN_BROWSER; - var resolvedKey = parentId + " " + key; - var insertMarkers = IS_SERVER ? willRerender : isToken; - if (insertMarkers) out.comment("F^" + resolvedKey); + var preserve = IS_SERVER ? willRerender : isToken; + out.___beginFragment(key, component, preserve); if (isFn) { + var componentsContext = getComponentsContext(out); + var parentComponentDef = + componentsContext.___componentDef; + var globalContext = + componentsContext.___globalContext; + componentsContext.___componentDef = new ComponentDef( + component, + parentComponentDef.id + + "-" + + parentComponentDef.___nextKey(key), + globalContext + ); render.toJSON = RENDER_BODY_TO_JSON; render(out, attrs); + componentsContext.___componentDef = parentComponentDef; } - if (insertMarkers) out.comment("F/" + resolvedKey); + out.___endFragment(); } else { out.error("Invalid dynamic tag value"); } diff --git a/src/runtime/html/AsyncStream.js b/src/runtime/html/AsyncStream.js index 1648234e6..ab3e915a8 100644 --- a/src/runtime/html/AsyncStream.js +++ b/src/runtime/html/AsyncStream.js @@ -500,6 +500,24 @@ var proto = (AsyncStream.prototype = { this.write(escapeXml(str)); }, + ___beginFragment: function(key, component, preserve) { + if (preserve) { + this.write(""); + } + if (this._elStack) { + this._elStack.push(preserve); + } else { + this._elStack = [preserve]; + } + }, + + ___endFragment: function() { + var preserve = this._elStack.pop(); + if (preserve) { + this.write(""); + } + }, + ___getNode: function(doc) { var node = this._node; var curEl; diff --git a/src/runtime/vdom/AsyncVDOMBuilder.js b/src/runtime/vdom/AsyncVDOMBuilder.js index 20882ddc3..7ad8ea6e7 100644 --- a/src/runtime/vdom/AsyncVDOMBuilder.js +++ b/src/runtime/vdom/AsyncVDOMBuilder.js @@ -5,6 +5,7 @@ var VDocumentFragment = vdom.___VDocumentFragment; var VComment = vdom.___VComment; var VText = vdom.___VText; var VComponent = vdom.___VComponent; +var VFragment = vdom.___VFragment; var virtualizeHTML = vdom.___virtualizeHTML; var RenderResult = require("../RenderResult"); var defaultDocument = vdom.___defaultDocument; @@ -56,13 +57,13 @@ var proto = (AsyncVDOMBuilder.prototype = { ___isOut: true, ___document: defaultDocument, - bc: function(component) { - var vComponent = new VComponent(component); + bc: function(component, key, ownerComponent) { + var vComponent = new VComponent(component, key, ownerComponent); return this.___beginNode(vComponent, 0, true); }, - ___preserveComponent: function(component) { - var vComponent = new VComponent(component, true); + ___preserveComponent: function(component, key, ownerComponent) { + var vComponent = new VComponent(component, key, ownerComponent, true); this.___beginNode(vComponent, 0); }, @@ -122,7 +123,7 @@ var proto = (AsyncVDOMBuilder.prototype = { // and a node can only have one parent node. var clone = node.___cloneNode(); this.node(clone); - clone.___component = component; + clone.___ownerComponent = component; return this; }, @@ -208,6 +209,16 @@ var proto = (AsyncVDOMBuilder.prototype = { return this; }, + ___beginFragment: function(key, component, preserve) { + var fragment = new VFragment(key, component, preserve); + this.___beginNode(fragment, null, true); + return this; + }, + + ___endFragment: function() { + this.endElement(); + }, + endElement: function() { var stack = this.___stack; stack.pop(); @@ -417,7 +428,7 @@ var proto = (AsyncVDOMBuilder.prototype = { // Create the root document fragment node doc = doc || this.___document || document; this.___vnode = node = vdomTree.___actualize(doc); - morphdom(node, null, null, vdomTree, doc, this.___components); + morphdom(node, vdomTree, doc, this.___components); } return node; }, diff --git a/src/runtime/vdom/VComponent.js b/src/runtime/vdom/VComponent.js index ac9d2aebf..718415499 100644 --- a/src/runtime/vdom/VComponent.js +++ b/src/runtime/vdom/VComponent.js @@ -1,9 +1,11 @@ var VNode = require("./VNode"); var inherit = require("raptor-util/inherit"); -function VComponent(component, preserve) { +function VComponent(component, key, ownerComponent, preserve) { this.___VNode(null /* childCount */); + this.___key = key; this.___component = component; + this.___ownerComponent = ownerComponent; this.___preserve = preserve; } diff --git a/src/runtime/vdom/VElement.js b/src/runtime/vdom/VElement.js index cb151176a..de7034c0a 100644 --- a/src/runtime/vdom/VElement.js +++ b/src/runtime/vdom/VElement.js @@ -60,7 +60,15 @@ function VElementClone(other) { this.___isTextArea = other.___isTextArea; } -function VElement(tagName, attrs, key, component, childCount, flags, props) { +function VElement( + tagName, + attrs, + key, + ownerComponent, + childCount, + flags, + props +) { this.___VNode(childCount); var constId; @@ -81,7 +89,7 @@ function VElement(tagName, attrs, key, component, childCount, flags, props) { } this.___key = key; - this.___component = component; + this.___ownerComponent = ownerComponent; this.___attributes = attrs || EMPTY_OBJECT; this.___properties = props || EMPTY_OBJECT; this.___namespaceURI = namespaceURI; @@ -105,13 +113,13 @@ VElement.prototype = { * @param {int|null} attrCount The number of attributes (or `null` if not known) * @param {int|null} childCount The number of child nodes (or `null` if not known) */ - e: function(tagName, attrs, key, component, childCount, flags, props) { + e: function(tagName, attrs, key, ownerComponent, childCount, flags, props) { var child = this.___appendChild( new VElement( tagName, attrs, key, - component, + ownerComponent, childCount, flags, props @@ -132,13 +140,21 @@ VElement.prototype = { * @param {int|null} attrCount The number of attributes (or `null` if not known) * @param {int|null} childCount The number of child nodes (or `null` if not known) */ - ed: function(tagName, attrs, key, component, childCount, flags, props) { + ed: function( + tagName, + attrs, + key, + ownerComponent, + childCount, + flags, + props + ) { var child = this.___appendChild( VElement.___createElementDynamicTag( tagName, attrs, key, - component, + ownerComponent, childCount, flags, props @@ -158,9 +174,9 @@ VElement.prototype = { * * @param {String} value The value for the new Comment node */ - n: function(node, component) { + n: function(node, ownerComponent) { node = node.___cloneNode(); - node.___component = component; + node.___ownerComponent = ownerComponent; this.___appendChild(node); return this.___finishChild(); }, @@ -238,7 +254,12 @@ defineProperty(proto, "___value", { if (value == null) { value = this.___attributes.value; } - return value != null ? toString(value) : ""; + return value != null + ? toString(value) + : this.___attributes.type === "checkbox" || + this.___attributes.type === "radio" + ? "on" + : ""; } }); @@ -246,7 +267,7 @@ VElement.___createElementDynamicTag = function( tagName, attrs, key, - component, + ownerComponent, childCount, flags, props @@ -257,7 +278,7 @@ VElement.___createElementDynamicTag = function( tagName, attrs, key, - component, + ownerComponent, childCount, flags, props @@ -307,7 +328,7 @@ function virtualizeElement(node, virtualizeChildNodes) { tagName, attrs, null /*key*/, - null /*component*/, + null /*ownerComponent*/, 0 /*child count*/, flags, null /*props*/ diff --git a/src/runtime/vdom/VFragment.js b/src/runtime/vdom/VFragment.js new file mode 100644 index 000000000..c5cd62466 --- /dev/null +++ b/src/runtime/vdom/VFragment.js @@ -0,0 +1,25 @@ +var VNode = require("./VNode"); +var inherit = require("raptor-util/inherit"); +var createFragmentNode = require("../../morphdom/fragment") + .___createFragmentNode; + +function VFragment(key, ownerComponent, preserve) { + this.___VNode(null /* childCount */); + this.___key = key; + this.___ownerComponent = ownerComponent; + this.___preserve = preserve; +} + +VFragment.prototype = { + ___nodeType: 12, + ___actualize: function() { + var fragment = createFragmentNode(); + fragment.___markoKey = this.___key; + fragment.___markoVElement = this; + return fragment; + } +}; + +inherit(VFragment, VNode); + +module.exports = VFragment; diff --git a/src/runtime/vdom/VNode.js b/src/runtime/vdom/VNode.js index d0debbcdd..f2fc9bb67 100644 --- a/src/runtime/vdom/VNode.js +++ b/src/runtime/vdom/VNode.js @@ -11,7 +11,7 @@ VNode.prototype = { this.___nextSiblingInternal = null; }, - ___component: null, + ___ownerComponent: null, get ___firstChild() { var firstChild = this.___firstChildInternal; diff --git a/src/runtime/vdom/vdom.js b/src/runtime/vdom/vdom.js index 24bdd4d4f..9089bc96e 100644 --- a/src/runtime/vdom/vdom.js +++ b/src/runtime/vdom/vdom.js @@ -4,6 +4,7 @@ var VDocumentFragment = require("./VDocumentFragment"); var VElement = require("./VElement"); var VText = require("./VText"); var VComponent = require("./VComponent"); +var VFragment = require("./VFragment"); var defaultDocument = typeof document != "undefined" && document; var specialHtmlRegexp = /[&<]/; @@ -91,6 +92,7 @@ exports.___VDocumentFragment = VDocumentFragment; exports.___VElement = VElement; exports.___VText = VText; exports.___VComponent = VComponent; +exports.___VFragment = VFragment; exports.___virtualize = virtualize; exports.___virtualizeHTML = virtualizeHTML; exports.___defaultDocument = defaultDocument; diff --git a/src/taglibs/core/include-tag.js b/src/taglibs/core/include-tag.js index 40b7dc020..15d760473 100644 --- a/src/taglibs/core/include-tag.js +++ b/src/taglibs/core/include-tag.js @@ -37,15 +37,18 @@ function doInclude(input, out, throwError) { var willRerenderInBrowser = componentDef && componentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER; - var key = (componentDef && componentDef.id) + " " + out.___assignedKey; var isFn = renderBody !== RENDER_BODY_TOKEN; - var insertMarkers = (IS_SERVER && willRerenderInBrowser) || !isFn; - if (insertMarkers) out.comment("F^" + key); - if (renderBody !== RENDER_BODY_TOKEN) { + var preserve = (IS_SERVER && willRerenderInBrowser) || !isFn; + out.___beginFragment( + out.___assignedKey, + componentDef && componentDef.___component, + preserve + ); + if (isFn) { renderBody.toJSON = RENDER_BODY_TO_JSON; renderBody(out, arg); } - if (insertMarkers) out.comment("F/" + key); + out.___endFragment(); } } diff --git a/test/compiler/fixtures-vdom/svg-anchor/expected.js b/test/compiler/fixtures-vdom/svg-anchor/expected.js index eed4d35db..0fcf01251 100644 --- a/test/compiler/fixtures-vdom/svg-anchor/expected.js +++ b/test/compiler/fixtures-vdom/svg-anchor/expected.js @@ -21,7 +21,7 @@ var marko_template = module.exports = require("marko/src/vdom").t(), .e("a", { "xlink:href": "https://developer.mozilla.org/en-US/docs/SVG", target: "_blank" - }, "1", null, 0, 1); + }, null, null, 0, 1); function render(input, out, __component, component, state) { var data = input; diff --git a/test/compiler/fixtures-vdom/svg/expected.js b/test/compiler/fixtures-vdom/svg/expected.js index bd73312a7..a4a033a23 100644 --- a/test/compiler/fixtures-vdom/svg/expected.js +++ b/test/compiler/fixtures-vdom/svg/expected.js @@ -22,7 +22,7 @@ var marko_template = module.exports = require("marko/src/vdom").t(), cx: "100", cy: "100", r: "100" - }, "1", null, 0, 1); + }, null, null, 0, 1); function render(input, out, __component, component, state) { var data = input; diff --git a/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/components/hello.marko b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/components/hello.marko new file mode 100644 index 000000000..1e6b6982e --- /dev/null +++ b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/components/hello.marko @@ -0,0 +1,7 @@ +class { + onCreate() { + this.message = "Hello"; + } +} + +
\ No newline at end of file diff --git a/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/index.marko b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/index.marko new file mode 100644 index 000000000..76c3b0ccd --- /dev/null +++ b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/index.marko @@ -0,0 +1,3 @@ +class {} + + diff --git a/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/test.js b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/test.js new file mode 100644 index 000000000..2a038a57f --- /dev/null +++ b/test/components-browser/fixtures-deprecated/repeated-key-as-single-component-ref/test.js @@ -0,0 +1,6 @@ +var expect = require("chai").expect; + +module.exports = function(helpers) { + var component = helpers.mount(require.resolve("./index"), {}); + expect(component.getComponent("something[]").message).to.equal("Hello"); +}; diff --git a/test/components-browser/fixtures-deprecated/widget-legacy-rerender/test.js b/test/components-browser/fixtures-deprecated/widget-legacy-rerender/test.js index a66c95301..8d3f9d351 100644 --- a/test/components-browser/fixtures-deprecated/widget-legacy-rerender/test.js +++ b/test/components-browser/fixtures-deprecated/widget-legacy-rerender/test.js @@ -11,8 +11,8 @@ module.exports = function(helpers) { var nextSibling = document.createElement("div"); helpers.targetEl.appendChild(nextSibling); - expect(widget.el.previousSibling).to.equal(previousSibling); - expect(widget.el.nextSibling).to.equal(nextSibling); + expect(widget.el.previousElementSibling).to.equal(previousSibling); + expect(widget.el.nextElementSibling).to.equal(nextSibling); var parentNode = widget.el.parentNode; @@ -29,10 +29,10 @@ module.exports = function(helpers) { expect(widget.el.parentNode).to.equal(parentNode); // expect(widget.el !== oldEl).to.equal(true); - expect(helpers.targetEl.childNodes.length).to.equal(3); - expect(helpers.targetEl.childNodes[0]).to.equal(previousSibling); - expect(helpers.targetEl.childNodes[1]).to.equal(widget.el); - expect(helpers.targetEl.childNodes[2]).to.equal(nextSibling); + expect(helpers.targetEl.children.length).to.equal(3); + expect(helpers.targetEl.children[0]).to.equal(previousSibling); + expect(helpers.targetEl.children[1]).to.equal(widget.el); + expect(helpers.targetEl.children[2]).to.equal(nextSibling); }; module.exports.skip_hydrate = "a split widget cannot re-render when hydrated"; diff --git a/test/components-browser/fixtures/diffpatch-boundary-inner-component-only/test.js b/test/components-browser/fixtures/diffpatch-boundary-inner-component-only/test.js index 0ec9920e9..d7a3be62f 100644 --- a/test/components-browser/fixtures/diffpatch-boundary-inner-component-only/test.js +++ b/test/components-browser/fixtures/diffpatch-boundary-inner-component-only/test.js @@ -4,18 +4,12 @@ module.exports = function(helpers) { var component = helpers.mount(require.resolve("./index"), {}); var innerComponent = component.getComponent("inner"); - expect(innerComponent.___startNode.className).to.equal("inner"); - expect(component.___startNode).to.not.equal(innerComponent.___startNode); - expect(component.___endNode).to.not.equal(innerComponent.___endNode); expect(helpers.targetEl.querySelector(".inner").innerHTML).to.equal("0"); component.state.count++; component.update(); - innerComponent = component.getComponent("inner"); + expect(component.getComponent("inner")).to.equal(innerComponent); - expect(innerComponent.___startNode.className).to.equal("inner"); - expect(component.___startNode).to.not.equal(innerComponent.___startNode); - expect(component.___endNode).to.not.equal(innerComponent.___endNode); expect(helpers.targetEl.querySelector(".inner").innerHTML).to.equal("1"); }; diff --git a/test/components-browser/fixtures/event-attach-if-else-nested-component/test.js b/test/components-browser/fixtures/event-attach-if-else-nested-component/test.js index eb6b4601c..877b11cd5 100644 --- a/test/components-browser/fixtures/event-attach-if-else-nested-component/test.js +++ b/test/components-browser/fixtures/event-attach-if-else-nested-component/test.js @@ -6,11 +6,18 @@ module.exports = function(helpers) { color: colors[0] }); - expect(component.events.length).to.equal(1); - expect(component.events[0].color).to.equal("blue"); - expect(component.events[0].node).to.equal( - component.el.querySelectorAll("li")[0] - ); + if (helpers.isHydrate) { + // When hydrating, the first color item was rendered on the + // server so there is no corresponding attach event f + // we'll push an empty event so the indexes line up + component.events.push(null); + } else { + expect(component.events.length).to.equal(1); + expect(component.events[0].color).to.equal("blue"); + expect(component.events[0].node).to.equal( + component.el.querySelectorAll("li")[0] + ); + } component.input = { color: colors[1] }; component.update(); diff --git a/test/components-browser/fixtures/force-update-dom-exception-3/test.js b/test/components-browser/fixtures/force-update-dom-exception-3/test.js index deddfa1e7..90ead898c 100644 --- a/test/components-browser/fixtures/force-update-dom-exception-3/test.js +++ b/test/components-browser/fixtures/force-update-dom-exception-3/test.js @@ -34,5 +34,3 @@ module.exports = function(helpers) { component.update(); }); }; - -module.exports.fails = "issue #1059"; diff --git a/test/components-browser/fixtures/keyed-matching-transcluded/index.marko b/test/components-browser/fixtures/keyed-matching-transcluded/index.marko index 66f53f01c..d9a3acd90 100644 --- a/test/components-browser/fixtures/keyed-matching-transcluded/index.marko +++ b/test/components-browser/fixtures/keyed-matching-transcluded/index.marko @@ -1,7 +1,7 @@ class {} - + ${i}
diff --git a/test/components-browser/fixtures/keyed-matching-transcluded/test.js b/test/components-browser/fixtures/keyed-matching-transcluded/test.js index dc3bd65dc..18f7161eb 100644 --- a/test/components-browser/fixtures/keyed-matching-transcluded/test.js +++ b/test/components-browser/fixtures/keyed-matching-transcluded/test.js @@ -5,6 +5,6 @@ module.exports = function(helpers) { var targetEl = helpers.targetEl; var innerHTML = targetEl.innerHTML; expect(innerHTML).to.equal( - '
1
2
3
4
' + '
1
2
3
4
' ); }; diff --git a/test/components-browser/fixtures/nested-fragments/test.js b/test/components-browser/fixtures/nested-fragments/test.js index 86b7cc838..8a039b648 100644 --- a/test/components-browser/fixtures/nested-fragments/test.js +++ b/test/components-browser/fixtures/nested-fragments/test.js @@ -3,5 +3,3 @@ module.exports = function(helpers) { component.setState("showLast", false); component.update(); }; - -module.exports.fails_hydrate = "issue #1051"; diff --git a/test/components-browser/fixtures/replace-async/test.js b/test/components-browser/fixtures/replace-async/test.js index fb75ac58e..c69471f6d 100644 --- a/test/components-browser/fixtures/replace-async/test.js +++ b/test/components-browser/fixtures/replace-async/test.js @@ -9,8 +9,10 @@ module.exports = function(helpers, done) { .render({ name: "John" }) .then(function(result) { result.replace(targetEl); - expect(component.el.firstChild.className).to.equal("hello"); - expect(component.el.firstChild.innerHTML).to.equal("Hello John"); + expect(component.el.firstElementChild.className).to.equal("hello"); + expect(component.el.firstElementChild.innerHTML).to.equal( + "Hello John" + ); done(); }) .catch(function(err) { diff --git a/test/components-browser/fixtures/replace/test.js b/test/components-browser/fixtures/replace/test.js index 48146c9c6..4e96084ec 100644 --- a/test/components-browser/fixtures/replace/test.js +++ b/test/components-browser/fixtures/replace/test.js @@ -7,6 +7,6 @@ module.exports = function(helpers) { var targetEl = component.getEl("target"); hello.renderSync({ name: "John" }).replace(targetEl); - expect(component.el.firstChild.className).to.equal("hello"); - expect(component.el.firstChild.innerHTML).to.equal("Hello John"); + expect(component.el.firstElementChild.className).to.equal("hello"); + expect(component.el.firstElementChild.innerHTML).to.equal("Hello John"); }; diff --git a/test/components-browser/fixtures/server-client-mismatch/components/app-bar/index.marko b/test/components-browser/fixtures/server-client-mismatch/components/app-bar/index.marko new file mode 100644 index 000000000..8fb0a90a9 --- /dev/null +++ b/test/components-browser/fixtures/server-client-mismatch/components/app-bar/index.marko @@ -0,0 +1,12 @@ +class { + onMount() { + + } +} + + + Hello + + + World + \ No newline at end of file diff --git a/test/components-browser/fixtures/server-client-mismatch/components/app-foo/index.marko b/test/components-browser/fixtures/server-client-mismatch/components/app-foo/index.marko new file mode 100644 index 000000000..a5cae63c3 --- /dev/null +++ b/test/components-browser/fixtures/server-client-mismatch/components/app-foo/index.marko @@ -0,0 +1,8 @@ +class { + onMount() { + + } +} + + + \ No newline at end of file diff --git a/test/components-browser/fixtures/server-client-mismatch/index.marko b/test/components-browser/fixtures/server-client-mismatch/index.marko new file mode 100644 index 000000000..daabcb5b6 --- /dev/null +++ b/test/components-browser/fixtures/server-client-mismatch/index.marko @@ -0,0 +1,10 @@ +class { + onMount() { + + } +} + + + + eBay + \ No newline at end of file diff --git a/test/components-browser/fixtures/server-client-mismatch/test.js b/test/components-browser/fixtures/server-client-mismatch/test.js new file mode 100644 index 000000000..b0cee37ff --- /dev/null +++ b/test/components-browser/fixtures/server-client-mismatch/test.js @@ -0,0 +1,16 @@ +var expect = require("chai").expect; + +module.exports = function(helpers) { + var component = helpers.mount(require.resolve("./index"), {}); + + expect(helpers.targetEl.innerHTML).to.equal( + '
eBay' + ); + + component.forceUpdate(); + component.update(); + + expect(helpers.targetEl.innerHTML).to.equal( + '
eBay' + ); +}; diff --git a/test/components-browser/fixtures/top-level-transcluded-content/test.js b/test/components-browser/fixtures/top-level-transcluded-content/test.js index daeabec2c..c2655e520 100644 --- a/test/components-browser/fixtures/top-level-transcluded-content/test.js +++ b/test/components-browser/fixtures/top-level-transcluded-content/test.js @@ -14,5 +14,3 @@ module.exports = function(helpers) { component.update(); expect(helpers.targetEl.textContent).to.equal("Hello world"); }; - -module.exports.fails = "issue #1033"; diff --git a/test/components-browser/fixtures/transcluded-component-from-non-rerender-root/test.js b/test/components-browser/fixtures/transcluded-component-from-non-rerender-root/test.js index 50fd2cbcb..ce26a01f0 100644 --- a/test/components-browser/fixtures/transcluded-component-from-non-rerender-root/test.js +++ b/test/components-browser/fixtures/transcluded-component-from-non-rerender-root/test.js @@ -8,24 +8,26 @@ module.exports = function(helpers) { expect(buttonComponent).to.exist; expect(countComponent).to.exist; - // TODO: enable this part of the test - /* - expect(buttonComponent.el.innerHTML).to.contain('0'); - expect(buttonComponent.el.className).to.equal('app-button app-button-small'); + expect(buttonComponent.el.innerHTML).to.contain("0"); + expect(buttonComponent.el.className).to.equal( + "app-button app-button-small" + ); - buttonComponent.setSize('large'); + buttonComponent.setSize("large"); buttonComponent.update(); - expect(buttonComponent.el.innerHTML).to.contxain('0'); - expect(buttonComponent.el.className).to.equal('app-button app-button-large'); + expect(buttonComponent.el.innerHTML).to.contain("0"); + expect(buttonComponent.el.className).to.equal( + "app-button app-button-large" + ); - debugger; countComponent.increment(); countComponent.update(); - expect(buttonComponent.el.innerHTML).to.contain('1'); + expect(buttonComponent.el.innerHTML).to.contain("1"); - buttonComponent.setSize('small'); + buttonComponent.setSize("small"); buttonComponent.update(); - expect(buttonComponent.el.innerHTML).to.contain('1'); - expect(buttonComponent.el.className).to.equal('app-button app-button-small'); - */ + expect(buttonComponent.el.innerHTML).to.contain("1"); + expect(buttonComponent.el.className).to.equal( + "app-button app-button-small" + ); }; diff --git a/test/components-browser/fixtures/transclusion-repeated-nested/components/container/index.marko b/test/components-browser/fixtures/transclusion-repeated-nested/components/container/index.marko new file mode 100644 index 000000000..30f7aac24 --- /dev/null +++ b/test/components-browser/fixtures/transclusion-repeated-nested/components/container/index.marko @@ -0,0 +1,3 @@ +class {} + +<${input}/> \ No newline at end of file diff --git a/test/components-browser/fixtures/transclusion-repeated-nested/components/counter/index.marko b/test/components-browser/fixtures/transclusion-repeated-nested/components/counter/index.marko new file mode 100644 index 000000000..c50a5e5ca --- /dev/null +++ b/test/components-browser/fixtures/transclusion-repeated-nested/components/counter/index.marko @@ -0,0 +1,11 @@ +class { + onCreate() { + this.state = { count:0 }; + } + + increment() { + this.state.count++; + } +} + +-- ${state.count} \ No newline at end of file diff --git a/test/components-browser/fixtures/transclusion-repeated-nested/components/list/index.marko b/test/components-browser/fixtures/transclusion-repeated-nested/components/list/index.marko new file mode 100644 index 000000000..5923c4d1b --- /dev/null +++ b/test/components-browser/fixtures/transclusion-repeated-nested/components/list/index.marko @@ -0,0 +1,5 @@ +class {} + + + <${input}/> + \ No newline at end of file diff --git a/test/components-browser/fixtures/transclusion-repeated-nested/index.marko b/test/components-browser/fixtures/transclusion-repeated-nested/index.marko new file mode 100644 index 000000000..60acc9fa1 --- /dev/null +++ b/test/components-browser/fixtures/transclusion-repeated-nested/index.marko @@ -0,0 +1,7 @@ +class {} + + + + + + \ No newline at end of file diff --git a/test/components-browser/fixtures/transclusion-repeated-nested/test.js b/test/components-browser/fixtures/transclusion-repeated-nested/test.js new file mode 100644 index 000000000..f9664721f --- /dev/null +++ b/test/components-browser/fixtures/transclusion-repeated-nested/test.js @@ -0,0 +1,33 @@ +var expect = require("chai").expect; + +module.exports = function(helpers) { + var root = helpers.mount(require.resolve("./index")); + var counters = root.getComponents("counter"); + var containers = root.getComponents("container"); + + expect(helpers.targetEl.textContent).to.equal("000"); + + counters[0].increment(); + counters[0].update(); + counters[1].increment(); + counters[1].increment(); + counters[1].update(); + counters[2].increment(); + counters[2].increment(); + counters[2].increment(); + counters[2].update(); + + expect(helpers.targetEl.textContent).to.equal("123"); + + containers[1].forceUpdate(); + containers[1].update(); + containers[2].forceUpdate(); + containers[2].update(); + + expect(helpers.targetEl.textContent).to.equal("123"); + + counters[2].increment(); + counters[2].update(); + + expect(helpers.targetEl.textContent).to.equal("124"); +}; diff --git a/test/components-browser/fixtures/transclusion-rerender-stateful-shared-key/components/container/index.marko b/test/components-browser/fixtures/transclusion-rerender-stateful-shared-key/components/container/index.marko index 74285ba3b..f38f3f477 100644 --- a/test/components-browser/fixtures/transclusion-rerender-stateful-shared-key/components/container/index.marko +++ b/test/components-browser/fixtures/transclusion-rerender-stateful-shared-key/components/container/index.marko @@ -4,6 +4,4 @@ class { } } -
- | -
\ No newline at end of file +|<${input}/> \ No newline at end of file diff --git a/test/components-browser/index.test.js b/test/components-browser/index.test.js index c42aaa4a2..e16d8d98a 100644 --- a/test/components-browser/index.test.js +++ b/test/components-browser/index.test.js @@ -33,13 +33,18 @@ function runClientTest(fixture) { if (isAsync) { testFunc(helpers, cleanupAndFinish); } else { - testFunc(helpers); - cleanupAndFinish(); + let err; + try { + testFunc(helpers); + } catch (e) { + err = e; + } + cleanupAndFinish(err); } function cleanupAndFinish(err) { // Cache components for use in hydrate run. - context.rendered = helpers.rendered; + if (!err) context.rendered = helpers.rendered; helpers.instances.forEach(instance => instance.destroy()); helpers.targetEl.innerHTML = ""; done(err); diff --git a/test/components-compilation/fixtures-html/auto-key-els/expected.js b/test/components-compilation/fixtures-html/auto-key-els/expected.js index 74d305008..761b92e07 100644 --- a/test/components-compilation/fixtures-html/auto-key-els/expected.js +++ b/test/components-compilation/fixtures-html/auto-key-els/expected.js @@ -32,7 +32,7 @@ function render(input, out, __component, component, state) { out.w(""); - var __key5 = __component.___nextKey("preservedP"); + var __key5 = __component.___nextKey("@preservedP"); out.w("

"); diff --git a/test/components-compilation/fixtures-vdom/boundary-html-tag/expected.js b/test/components-compilation/fixtures-vdom/boundary-html-tag/expected.js index 460a4b278..b93df5da9 100644 --- a/test/components-compilation/fixtures-vdom/boundary-html-tag/expected.js +++ b/test/components-compilation/fixtures-vdom/boundary-html-tag/expected.js @@ -20,7 +20,7 @@ var marko_template = module.exports = require("marko/src/vdom").t(__filename), marko_node0 = marko_createElement("HEAD", null, "1", null, 1, 0, { i: marko_const_nextId() }) - .e("TITLE", null, "2", null, 1) + .e("TITLE", null, null, null, 1) .t("Hello"), marko_node1 = marko_createElement("H1", null, "4", null, 1, 0, { i: marko_const_nextId() diff --git a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js index bfc81b3bc..72d50afb0 100644 --- a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js +++ b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js @@ -18,7 +18,7 @@ var marko_template = module.exports = require("marko/src/vdom").t(__filename), function render(input, out, __component, component, state) { var data = input; - out.e("H1", null, input.myStartKey, component, 0); + out.e("H1", null, "@" + input.myStartKey, component, 0); my_component_tag({}, out, __component, "0"); } diff --git a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys-dynamic/expected.js b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys-dynamic/expected.js index f4a7594cb..27d67924e 100644 --- a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys-dynamic/expected.js +++ b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys-dynamic/expected.js @@ -13,9 +13,9 @@ var marko_template = module.exports = require("marko/src/vdom").t(__filename), function render(input, out, __component, component, state) { var data = input; - out.e("H1", null, input.myStartKey, component, 0); + out.e("H1", null, "@" + input.myStartKey, component, 0); - out.e("DIV", null, input.myEndKey, component, 0); + out.e("DIV", null, "@" + input.myEndKey, component, 0); } marko_template._ = marko_renderer(render, { diff --git a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys/expected.js b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys/expected.js index 2eb55cfb8..a5e1dfd22 100644 --- a/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys/expected.js +++ b/test/components-compilation/fixtures-vdom/boundary-multi-root-html-els-keys/expected.js @@ -13,10 +13,10 @@ var marko_template = module.exports = require("marko/src/vdom").t(__filename), marko_createElement = marko_helpers.e, marko_const = marko_helpers.const, marko_const_nextId = marko_const("339bea"), - marko_node0 = marko_createElement("H1", null, "myStart", null, 0, 0, { + marko_node0 = marko_createElement("H1", null, "@myStart", null, 0, 0, { i: marko_const_nextId() }), - marko_node1 = marko_createElement("DIV", null, "myEnd", null, 0, 0, { + marko_node1 = marko_createElement("DIV", null, "@myEnd", null, 0, 0, { i: marko_const_nextId() }); diff --git a/test/components-pages/fixtures/if-empty-component/tests.js b/test/components-pages/fixtures/if-empty-component/tests.js index 230c3a022..e8a3a7936 100644 --- a/test/components-pages/fixtures/if-empty-component/tests.js +++ b/test/components-pages/fixtures/if-empty-component/tests.js @@ -6,7 +6,7 @@ describe(path.basename(__dirname), function() { var app = window.app; app.forceUpdate(); app.update(); - expect(app.getEl().outerHTML).to.equal(undefined); + expect(app.getEl()).to.equal(undefined); app.input = { show: true }; app.forceUpdate(); app.update(); diff --git a/test/components-pages/fixtures/preserve-transcluded-content-on-state-change/tests.js b/test/components-pages/fixtures/preserve-transcluded-content-on-state-change/tests.js index bf701d7e8..f19b096c9 100644 --- a/test/components-pages/fixtures/preserve-transcluded-content-on-state-change/tests.js +++ b/test/components-pages/fixtures/preserve-transcluded-content-on-state-change/tests.js @@ -2,7 +2,7 @@ var path = require("path"); var expect = require("chai").expect; describe(path.basename(__dirname), function() { - it.fails("should update correctly", function() { + it("should update correctly", function() { var component = window.component; var $button = component.getEl("button"); expect($button.textContent).to.eql("button label"); @@ -10,7 +10,6 @@ describe(path.basename(__dirname), function() { $button.click(); component.update(); - expect($button.textContent).to.eql("button label test"); - }).details = - "issue #912"; + expect($button.textContent).to.eql("button labeltest"); + }); }); diff --git a/test/components-pages/fixtures/split-async-keys/components/app-child/index.marko b/test/components-pages/fixtures/split-async-keys/components/app-child/index.marko new file mode 100644 index 000000000..7cf10161c --- /dev/null +++ b/test/components-pages/fixtures/split-async-keys/components/app-child/index.marko @@ -0,0 +1,2 @@ +class {} +

\ No newline at end of file diff --git a/test/components-pages/fixtures/split-async-keys/components/app-hello/component-browser.js b/test/components-pages/fixtures/split-async-keys/components/app-hello/component-browser.js new file mode 100644 index 000000000..bc8e53a25 --- /dev/null +++ b/test/components-pages/fixtures/split-async-keys/components/app-hello/component-browser.js @@ -0,0 +1,5 @@ +module.exports = { + onMount() { + window.component = this; + } +}; diff --git a/test/components-pages/fixtures/split-async-keys/components/app-hello/index.marko b/test/components-pages/fixtures/split-async-keys/components/app-hello/index.marko new file mode 100644 index 000000000..190f4c00e --- /dev/null +++ b/test/components-pages/fixtures/split-async-keys/components/app-hello/index.marko @@ -0,0 +1,6 @@ +$ var promise = new Promise(resolve => setTimeout(resolve, 100)); + + +
+ + \ No newline at end of file diff --git a/test/components-pages/fixtures/split-async-keys/template.marko b/test/components-pages/fixtures/split-async-keys/template.marko new file mode 100644 index 000000000..ca8c88807 --- /dev/null +++ b/test/components-pages/fixtures/split-async-keys/template.marko @@ -0,0 +1,16 @@ + + + + + + + Marko Test + + +
+
+
+ + + + diff --git a/test/components-pages/fixtures/split-async-keys/tests.js b/test/components-pages/fixtures/split-async-keys/tests.js new file mode 100644 index 000000000..0a294afe4 --- /dev/null +++ b/test/components-pages/fixtures/split-async-keys/tests.js @@ -0,0 +1,10 @@ +var path = require("path"); +var expect = require("chai").expect; + +describe(path.basename(__dirname), function() { + it("should initialize components correctly across async boundaries", function(done) { + expect(window.component.getEl("div")).to.not.equal(undefined); + expect(window.component.getComponent("child")).to.not.equal(undefined); + done(); + }); +}); diff --git a/test/components-pages/index.test.js b/test/components-pages/index.test.js index 09cd5ae10..8c3725739 100644 --- a/test/components-pages/index.test.js +++ b/test/components-pages/index.test.js @@ -18,18 +18,24 @@ function run(fixture) { var testFile = resolve("tests.js"); var templateFile = resolve("template.marko"); var template = require(templateFile); - return template.render({}).then(function(html) { - var browser = createBrowserWithMarko(__dirname, String(html), { - beforeParse(window, browser) { - browser.require("../../components"); - browser.require(templateFile); - } + return template + .render({}) + .then(function(html) { + var browser = createBrowserWithMarko(__dirname, String(html), { + beforeParse(window, browser) { + browser.require("../../components"); + browser.window.$initComponents(); + browser.require(templateFile); + } + }); + after(function() { + browser.window.close(); + }); + return browser; + }) + .then(function(browser) { + browser.window.document.close(); + browser.require(testFile); }); - after(function() { - browser.window.close(); - }); - browser.require(testFile); - browser.window.$initComponents(); - }); }); } diff --git a/test/hot-reload/fixtures/component-to-template/initial-expected.html b/test/hot-reload/fixtures/component-to-template/initial-expected.html index 73bba7183..4cf543ef2 100644 --- a/test/hot-reload/fixtures/component-to-template/initial-expected.html +++ b/test/hot-reload/fixtures/component-to-template/initial-expected.html @@ -1 +1 @@ -
Hello Frank
\ No newline at end of file +
Hello Frank
\ No newline at end of file diff --git a/test/hot-reload/fixtures/template-export-component/initial-expected.html b/test/hot-reload/fixtures/template-export-component/initial-expected.html index 73bba7183..4cf543ef2 100644 --- a/test/hot-reload/fixtures/template-export-component/initial-expected.html +++ b/test/hot-reload/fixtures/template-export-component/initial-expected.html @@ -1 +1 @@ -
Hello Frank
\ No newline at end of file +
Hello Frank
\ No newline at end of file diff --git a/test/hot-reload/fixtures/template-export-component/reloaded-expected.html b/test/hot-reload/fixtures/template-export-component/reloaded-expected.html index 397264ab9..63ae838e9 100644 --- a/test/hot-reload/fixtures/template-export-component/reloaded-expected.html +++ b/test/hot-reload/fixtures/template-export-component/reloaded-expected.html @@ -1 +1 @@ -
Hello Frank
\ No newline at end of file +
Hello Frank
\ No newline at end of file diff --git a/test/hot-reload/fixtures/template-to-component/reloaded-expected.html b/test/hot-reload/fixtures/template-to-component/reloaded-expected.html index 9396e65a3..4f5101bfb 100644 --- a/test/hot-reload/fixtures/template-to-component/reloaded-expected.html +++ b/test/hot-reload/fixtures/template-to-component/reloaded-expected.html @@ -1 +1 @@ -
Hello Jane
\ No newline at end of file +
Hello Jane
\ No newline at end of file diff --git a/test/morphdom/index.test.js b/test/morphdom/index.test.js index be10c1fa7..b8fc2f835 100644 --- a/test/morphdom/index.test.js +++ b/test/morphdom/index.test.js @@ -42,9 +42,6 @@ autotest("fixtures", fixture => { encoding: "utf8" }); - var parentNode = fromNode; - var startNode = fromNode.firstChild; - var endNode = null; var toNode = targetVEl; var doc = fromDocument; var componentsContext = { @@ -55,14 +52,7 @@ autotest("fixtures", fixture => { } }; - morphdom( - parentNode, - startNode, - endNode, - toNode, - doc, - componentsContext - ); + morphdom(fromNode, toNode, doc, componentsContext); var actualHTML = serializeNode(fromNode); snapshot(actualHTML, ".html"); diff --git a/test/render/fixtures-async/components-await-beginAsync/expected.html b/test/render/fixtures-async/components-await-beginAsync/expected.html index 6b8af3cb6..d3744897f 100644 --- a/test/render/fixtures-async/components-await-beginAsync/expected.html +++ b/test/render/fixtures-async/components-await-beginAsync/expected.html @@ -1 +1 @@ -
Hello inner-inner
Hello inner
Hello outer
\ No newline at end of file +
Hello inner-inner
Hello inner
Hello outer
\ No newline at end of file diff --git a/test/render/fixtures-async/components-await-title/expected.html b/test/render/fixtures-async/components-await-title/expected.html index c1a041848..f5b6f8c8b 100644 --- a/test/render/fixtures-async/components-await-title/expected.html +++ b/test/render/fixtures-async/components-await-title/expected.html @@ -1 +1 @@ -Welcome Frank
Hello
\ No newline at end of file +Welcome Frank
Hello
\ No newline at end of file diff --git a/test/render/fixtures-async/components-await-title/test.js b/test/render/fixtures-async/components-await-title/test.js new file mode 100644 index 000000000..7d90b34af --- /dev/null +++ b/test/render/fixtures-async/components-await-title/test.js @@ -0,0 +1 @@ +exports.skip_vdom = "server-rendered vdom comment boundaries are not a concern"; diff --git a/test/render/fixtures-async/components-await/expected.html b/test/render/fixtures-async/components-await/expected.html index 872aa9025..66b02c6a8 100644 --- a/test/render/fixtures-async/components-await/expected.html +++ b/test/render/fixtures-async/components-await/expected.html @@ -1 +1 @@ -
Hello inner-inner
Hello inner
Hello outer
\ No newline at end of file +
Hello inner-inner
Hello inner
Hello outer
\ No newline at end of file diff --git a/test/render/fixtures-deprecated/component-aria-key/expected.html b/test/render/fixtures-deprecated/component-aria-key/expected.html index 02082c63b..51de1b0c5 100644 --- a/test/render/fixtures-deprecated/component-aria-key/expected.html +++ b/test/render/fixtures-deprecated/component-aria-key/expected.html @@ -1 +1 @@ -
Submit the form thing
\ No newline at end of file +
Submit the form thing
\ No newline at end of file diff --git a/test/render/fixtures/component-aria-key/expected.html b/test/render/fixtures/component-aria-key/expected.html index 02082c63b..51de1b0c5 100644 --- a/test/render/fixtures/component-aria-key/expected.html +++ b/test/render/fixtures/component-aria-key/expected.html @@ -1 +1 @@ -
Submit the form thing
\ No newline at end of file +
Submit the form thing
\ No newline at end of file diff --git a/test/render/fixtures/component-elId/expected.html b/test/render/fixtures/component-elId/expected.html index 9d5005d70..a1a7b912d 100644 --- a/test/render/fixtures/component-elId/expected.html +++ b/test/render/fixtures/component-elId/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/render/fixtures/component-file-export-class/expected.html b/test/render/fixtures/component-file-export-class/expected.html index e6118344f..7325ecf5b 100644 --- a/test/render/fixtures/component-file-export-class/expected.html +++ b/test/render/fixtures/component-file-export-class/expected.html @@ -1 +1 @@ -
Hello Frank!
\ No newline at end of file +
Hello Frank!
\ No newline at end of file diff --git a/test/render/fixtures/component-file-export-class/test.js b/test/render/fixtures/component-file-export-class/test.js new file mode 100644 index 000000000..7d90b34af --- /dev/null +++ b/test/render/fixtures/component-file-export-class/test.js @@ -0,0 +1 @@ +exports.skip_vdom = "server-rendered vdom comment boundaries are not a concern"; diff --git a/test/render/fixtures/component-getElId/expected.html b/test/render/fixtures/component-getElId/expected.html index 9d5005d70..a1a7b912d 100644 --- a/test/render/fixtures/component-getElId/expected.html +++ b/test/render/fixtures/component-getElId/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/render/fixtures/component-label-for/expected.html b/test/render/fixtures/component-label-for/expected.html index cc50efc40..387b7b92a 100644 --- a/test/render/fixtures/component-label-for/expected.html +++ b/test/render/fixtures/component-label-for/expected.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/render/fixtures/component-safe-json/expected.html b/test/render/fixtures/component-safe-json/expected.html index 62986c7c0..aa3080f98 100644 --- a/test/render/fixtures/component-safe-json/expected.html +++ b/test/render/fixtures/component-safe-json/expected.html @@ -1 +1 @@ -Hello \ No newline at end of file +Hello \ No newline at end of file diff --git a/test/render/fixtures/components/expected.html b/test/render/fixtures/components/expected.html index 7cfd6d259..89d218c1d 100644 --- a/test/render/fixtures/components/expected.html +++ b/test/render/fixtures/components/expected.html @@ -1 +1 @@ -Components

foo1

bar1

bar2

foo-split1

split-child1

split-child2

foo-split2

split-child1

split-child2

foo2

bar1

bar2

foo-split1

split-child1

split-child2

foo-split2

split-child1

split-child2

split1

split-child1

split-child2

split2

split-child1

split-child2

\ No newline at end of file +Components

foo1

bar1

bar2

foo-split1

split-child1

split-child2

foo-split2

split-child1

split-child2

foo2

bar1

bar2

foo-split1

split-child1

split-child2

foo-split2

split-child1

split-child2

split1

split-child1

split-child2

split2

split-child1

split-child2

\ No newline at end of file