+
FooBar!
${encodeVNode({ 0: 'D1', 1: 'DB' })}
diff --git a/packages/qwik/src/core/client/vnode-namespace.ts b/packages/qwik/src/core/client/vnode-namespace.ts
index b19d7d4f2a7..8f7970a789a 100644
--- a/packages/qwik/src/core/client/vnode-namespace.ts
+++ b/packages/qwik/src/core/client/vnode-namespace.ts
@@ -100,22 +100,36 @@ export function vnode_getDomChildrenWithCorrectNamespacesToInsert(
return domChildren;
}
-/** This function clones an element with a different namespace, but without the children. */
-function cloneElementWithNamespace(
+/** This function clones an element with a different namespace, including the children */
+function cloneDomTreeWithNamespace(
element: Element,
elementName: string,
- namespace: string
+ namespace: string,
+ deep = false
): Element {
const newElement = element.ownerDocument.createElementNS(namespace, elementName);
- const attributes = element.attributes;
- for (const attribute of attributes) {
- const name = attribute.name;
- const value = attribute.value;
- if (!name || name === Q_PROPS_SEPARATOR) {
- continue;
+
+ // Copy all attributes
+ for (const attr of element.attributes) {
+ if (attr.name !== Q_PROPS_SEPARATOR) {
+ newElement.setAttribute(attr.name, attr.value);
}
- newElement.setAttribute(name, value);
}
+
+ if (deep) {
+ // Recursively clone all child nodes
+ for (const child of element.childNodes) {
+ const nodeType = child.nodeType;
+ if (nodeType === 3 /* Node.TEXT_NODE */) {
+ newElement.appendChild(child.cloneNode());
+ } else if (nodeType === 1 /* Node.ELEMENT_NODE */) {
+ newElement.appendChild(
+ cloneDomTreeWithNamespace(child as Element, (child as Element).localName, namespace, deep)
+ );
+ }
+ }
+ }
+
return newElement;
}
@@ -159,8 +173,15 @@ function vnode_cloneElementWithNamespace(
namespace = namespaceData.elementNamespace;
namespaceFlag = namespaceData.elementNamespaceFlag;
}
+ const vFirstChild = vnode_getFirstChild(vCursor);
- newChildElement = cloneElementWithNamespace(childElement, childElementTag, namespace);
+ newChildElement = cloneDomTreeWithNamespace(
+ childElement,
+ childElementTag,
+ namespace,
+ // deep if there is no vnode children, children are probably inserted via innerHTML
+ !vFirstChild
+ );
childElement.remove();
@@ -173,7 +194,7 @@ function vnode_cloneElementWithNamespace(
// Descend into children
// We need first get the first child, if any
- const vFirstChild = vnode_getFirstChild(vCursor);
+
// Then we can overwrite the cursor with newly created element.
// This is because we need to materialize the children before we assign new element
vCursor.element = newChildElement;
diff --git a/packages/qwik/src/core/client/vnode.ts b/packages/qwik/src/core/client/vnode.ts
index c3d96604931..7e77c0ce9de 100644
--- a/packages/qwik/src/core/client/vnode.ts
+++ b/packages/qwik/src/core/client/vnode.ts
@@ -1478,27 +1478,28 @@ const fastGetter =
(prototype: any, name: string): T => {
);
};
-const isQStyleElement = (node: Node | null): node is Element => {
+const hasQStyleAttribute = (element: Element): boolean => {
return (
- isElement(node) &&
- node.nodeName === 'STYLE' &&
- (node.hasAttribute(QScopedStyle) || node.hasAttribute(QStyle))
+ element.nodeName === 'STYLE' &&
+ (element.hasAttribute(QScopedStyle) || element.hasAttribute(QStyle))
);
};
+const hasPropsSeparator = (element: Element): boolean => {
+ return element.hasAttribute(Q_PROPS_SEPARATOR);
+};
+
const materializeFromDOM = (vParent: ElementVNode, firstChild: Node | null, vData?: string) => {
let vFirstChild: VNode | null = null;
- const skipStyleElements = () => {
- while (isQStyleElement(child)) {
- // skip over style elements, as those need to be moved to the head.
- // VNode pretends that `Hello
';
+ expect(vParent).toMatchVDOM(
+
+ Hello
+
+ );
+ });
+ it('should skip non-qwik elements on start', () => {
+ parent.innerHTML = 'HelloWorld
';
+ expect(vParent).toMatchVDOM(
+
+ World
+
+ );
+ });
+ it('should skip non-qwik elements on end', () => {
+ parent.innerHTML = 'Hello
World';
+ expect(vParent).toMatchVDOM(
+
+ Hello
+
+ );
+ });
+ it('should skip non-qwik elements in front of text nodes', () => {
+ parent.innerHTML = 'Hello
Worldtext';
+ expect(vParent).toMatchVDOM(
+
+ Hello
+ text
+
+ );
+ });
+ });
+
+ describe('materializing vNodeData', () => {
+ it('should skip non-qwik elements on start', () => {
+ parent.innerHTML = 'HelloWorld
';
+ document.qVNodeData.set(parent, '1{}');
+ expect(vParent).toMatchVDOM(
+
+ World
+
+
+ );
+ });
+ it('should skip non-qwik elements on end', () => {
+ parent.innerHTML = 'Hello
World';
+ document.qVNodeData.set(parent, '{}1');
+ expect(vParent).toMatchVDOM(
+
+
+ Hello
+
+ );
+ });
+ it('should skip non-qwik elements in front of text nodes', () => {
+ parent.innerHTML = 'World
Hellotext';
+ document.qVNodeData.set(parent, '1E{}');
+ expect(vParent).toMatchVDOM(
+
+ World
+ text
+
+
+ );
+ });
+ });
});
function markVNodeTreeAsDeleted(vNode: VNode) {
diff --git a/packages/qwik/src/core/shared/scheduler-document-position.unit.ts b/packages/qwik/src/core/shared/scheduler-document-position.unit.ts
index 537ef13d3d2..0a1db1ac5c6 100644
--- a/packages/qwik/src/core/shared/scheduler-document-position.unit.ts
+++ b/packages/qwik/src/core/shared/scheduler-document-position.unit.ts
@@ -23,7 +23,7 @@ describe('vnode_documentPosition', () => {
});
it('should compare two elements', () => {
- parent.innerHTML = '';
+ parent.innerHTML = '';
const b = vnode_getFirstChild(vParent) as ElementVNode;
const i = b.nextSibling as ElementVNode;
expect(vnode_documentPosition(b, i)).toBe(-1);
diff --git a/packages/qwik/src/core/shared/scheduler.unit.tsx b/packages/qwik/src/core/shared/scheduler.unit.tsx
index 860eec02277..86ea635131b 100644
--- a/packages/qwik/src/core/shared/scheduler.unit.tsx
+++ b/packages/qwik/src/core/shared/scheduler.unit.tsx
@@ -66,7 +66,7 @@ describe('scheduler', () => {
blockedChores,
runningChores
);
- document.body.innerHTML = '';
+ document.body.innerHTML = '';
vBody = vnode_newUnMaterializedElement(document.body);
vA = vnode_locate(vBody, document.querySelector('a') as Element) as ElementVNode;
vAHost = vnode_newVirtual();
@@ -289,7 +289,7 @@ describe('scheduler', () => {
blockedChores,
runningChores
);
- document.body.innerHTML = '';
+ document.body.innerHTML = '';
vnode_newUnMaterializedElement(document.body);
vBHost1 = vnode_newVirtual();
vBHost1.setProp('q:id', 'b1');
diff --git a/packages/qwik/src/core/tests/ssr-render.spec.tsx b/packages/qwik/src/core/tests/ssr-render.spec.tsx
index 84b20170fa5..c8217d4488f 100644
--- a/packages/qwik/src/core/tests/ssr-render.spec.tsx
+++ b/packages/qwik/src/core/tests/ssr-render.spec.tsx
@@ -242,7 +242,7 @@ describe('v2 ssr render', () => {
});
it('should render values from generator with stream from string', async () => {
- const { vNode } = await ssrRenderToDom(
+ const { document } = await ssrRenderToDom(
{async function (stream: SSRStreamWriter) {
@@ -256,7 +256,7 @@ describe('v2 ssr render', () => {
{ debug }
);
- expect(vNode).toMatchVDOM(
+ await expect(document.querySelector('ul')).toMatchDOM(
- raw: 0
- raw: 1
diff --git a/packages/qwik/src/testing/vdom-diff.unit-util.ts b/packages/qwik/src/testing/vdom-diff.unit-util.ts
index cb6cef33b86..0a7a1f2c281 100644
--- a/packages/qwik/src/testing/vdom-diff.unit-util.ts
+++ b/packages/qwik/src/testing/vdom-diff.unit-util.ts
@@ -48,7 +48,7 @@ import {
} from '../core/shared/utils/markers';
import { HANDLER_PREFIX } from '../core/client/vnode-diff';
import { prettyJSX } from './jsx';
-import { prettyHtml } from './html';
+import { isElement, prettyHtml } from './html';
import { QContainerValue } from '../core/shared/types';
import type { ElementVNode, VirtualVNode, VNode } from '../core/client/vnode-impl';
@@ -77,6 +77,12 @@ expect.extend({
message: () => 'Missing element',
};
}
+ if (!isElement(received)) {
+ return {
+ pass: false,
+ message: () => 'Received is not an element',
+ };
+ }
const receivedHTML = await format(prettyHtml(received), formatOptions);
const expectedHTML = await format(prettyJSX(expected), formatOptions);
return {