diff --git a/packages/preactement/src/define.ts b/packages/preactement/src/define.ts
index 953b9c2..c025b9a 100644
--- a/packages/preactement/src/define.ts
+++ b/packages/preactement/src/define.ts
@@ -10,7 +10,7 @@ import {
getElementAttributes,
getAsyncComponent,
} from '@component-elements/shared';
-import { parseHtml } from './parse';
+import { parseChildren } from './parse';
import { IOptions, ComponentFunction } from './model';
/* -----------------------------------
@@ -131,7 +131,7 @@ function onConnected(this: CustomElement) {
let children = this.__children;
if (!this.__mounted && !this.hasAttribute('server')) {
- children = h(parseHtml.call(this), {});
+ children = h(parseChildren.call(this), {});
}
this.__properties = { ...this.__slots, ...data, ...attributes };
diff --git a/packages/preactement/src/parse.ts b/packages/preactement/src/parse.ts
index b946c0f..41a2c09 100644
--- a/packages/preactement/src/parse.ts
+++ b/packages/preactement/src/parse.ts
@@ -1,26 +1,20 @@
import { h, ComponentFactory, Fragment } from 'preact';
-import {
- CustomElement,
- getDocument,
- getAttributeObject,
- selfClosingTags,
- getPropKey,
-} from '@component-elements/shared';
+import { CustomElement, parseHtml, selfClosingTags, getPropKey } from '@component-elements/shared';
/* -----------------------------------
*
- * parseHtml
+ * parseChildren
*
* -------------------------------- */
-function parseHtml(this: CustomElement): ComponentFactory<{}> {
- const dom = getDocument(this.innerHTML);
+function parseChildren(this: CustomElement): ComponentFactory<{}> {
+ const children = parseHtml(this.innerHTML);
- if (!dom) {
+ if (!children.length) {
return void 0;
}
- const result = convertToVDom.call(this, dom);
+ const result = convertToVDom.call(this, children);
return () => result;
}
@@ -31,27 +25,21 @@ function parseHtml(this: CustomElement): ComponentFactory<{}> {
*
* -------------------------------- */
-function convertToVDom(this: CustomElement, node: Element) {
- if (node.nodeType === 3) {
- return node.textContent?.trim() || '';
+function convertToVDom(this: CustomElement, [nodeName, {slot, ...props}, children]: any) {
+ if(typeof children === 'string') {
+ return children.trim();
}
- if (node.nodeType !== 1) {
+ if(nodeName === void 0) {
return null;
}
- const nodeName = String(node.nodeName).toLowerCase();
- const childNodes = Array.from(node.childNodes);
+ const childNodes = () => children.map((child) =>
+ child.length ? convertToVDom.call(this, child) : void 0
+ );
- const children = () => childNodes.map((child) => convertToVDom.call(this, child));
- const { slot, ...props } = getAttributeObject(node.attributes);
-
- if (nodeName === 'script') {
- return null;
- }
-
- if (nodeName === 'body') {
- return h(Fragment, {}, children());
+ if (nodeName === null) {
+ return h(Fragment, {}, childNodes());
}
if (selfClosingTags.includes(nodeName)) {
@@ -59,12 +47,12 @@ function convertToVDom(this: CustomElement, node: Element) {
}
if (slot) {
- this.__slots[getPropKey(slot)] = getSlotChildren(children());
+ this.__slots[getPropKey(slot)] = getSlotChildren(childNodes());
return null;
}
- return h(nodeName, props, children());
+ return h(nodeName, props, childNodes());
}
/* -----------------------------------
@@ -89,4 +77,4 @@ function getSlotChildren(children: JSX.Element[]) {
*
* -------------------------------- */
-export { parseHtml };
+export { parseChildren };
diff --git a/packages/preactement/tests/parse.spec.ts b/packages/preactement/tests/parse.spec.ts
index c1403cf..b546a2e 100644
--- a/packages/preactement/tests/parse.spec.ts
+++ b/packages/preactement/tests/parse.spec.ts
@@ -1,6 +1,7 @@
import { h } from 'preact';
import { mount } from 'enzyme';
-import { parseHtml } from '../src/parse';
+import { parseChildren } from '../src/parse';
+import { parseHtml } from '@component-elements/shared';
/* -----------------------------------
*
@@ -20,24 +21,31 @@ const testScript = ``;
* -------------------------------- */
describe('parse', () => {
- describe('parseHtml()', () => {
+ describe('parseChildren', () => {
+ it('correctly converts an HTML string into a VDom tree', () => {
+ const result = parseChildren.call({ innerHTML: testHtml });
+ const instance = mount(h(result, {}) as any);
+
+ expect(instance.find('h1').text()).toEqual(testHeading);
+ });
+
it('should correctly handle misformed html', () => {
const testText = 'testText';
- const result = parseHtml.call({ innerHTML: `
${testText}` });
+ const result = parseChildren.call({ innerHTML: `${testText}` });
const instance = mount(h(result, {}) as any);
expect(instance.html()).toEqual(`${testText}
`);
});
it('handles text values witin custom element', () => {
- const result = parseHtml.call({ innerHTML: testHeading });
+ const result = parseChildren.call({ innerHTML: testHeading });
const instance = mount(h(result, {}) as any);
expect(instance.text()).toEqual(testHeading);
});
it('handles whitespace within custom element', () => {
- const result = parseHtml.call({ innerHTML: testWhitespace });
+ const result = parseChildren.call({ innerHTML: testWhitespace });
const instance = mount(h(result, {}) as any);
expect(instance.text()).toEqual('');
@@ -45,17 +53,13 @@ describe('parse', () => {
});
it('removes script blocks for security', () => {
- const result = parseHtml.call({ innerHTML: testScript });
- const instance = mount(h(result, {}) as any);
+ const result = parseChildren.call({ innerHTML: testScript });
- expect(instance.text()).toEqual('');
- });
+ console.log(parseHtml(testScript));
- it('correctly converts an HTML string into a VDom tree', () => {
- const result = parseHtml.call({ innerHTML: testHtml });
const instance = mount(h(result, {}) as any);
- expect(instance.find('h1').text()).toEqual(testHeading);
+ expect(instance.text()).toEqual('');
});
describe('slots', () => {
@@ -69,7 +73,7 @@ describe('parse', () => {
const headingHtml = `${testHeading}
`;
const testHtml = `${headingHtml}${slotHtml}`;
- const result = parseHtml.call({ innerHTML: testHtml, __slots: slots });
+ const result = parseChildren.call({ innerHTML: testHtml, __slots: slots });
const instance = mount(h(result, {}) as any);
expect(instance.html()).toEqual(``);
diff --git a/packages/reactement/src/define.ts b/packages/reactement/src/define.ts
index 345285f..f0a61b2 100644
--- a/packages/reactement/src/define.ts
+++ b/packages/reactement/src/define.ts
@@ -11,7 +11,7 @@ import {
getElementAttributes,
getAsyncComponent,
} from '@component-elements/shared';
-import { parseHtml } from './parse';
+import { parseChildren } from './parse';
import { IOptions, ComponentFunction } from './model';
/* -----------------------------------
@@ -132,7 +132,7 @@ function onConnected(this: CustomElement) {
let children = this.__children;
if (!this.__mounted && !this.hasAttribute('server')) {
- children = createElement(parseHtml.call(this), {});
+ children = createElement(parseChildren.call(this), {});
}
this.__properties = { ...this.__slots, ...data, ...attributes };
diff --git a/packages/reactement/src/parse.ts b/packages/reactement/src/parse.ts
index 4f0ded8..02b54dd 100644
--- a/packages/reactement/src/parse.ts
+++ b/packages/reactement/src/parse.ts
@@ -1,26 +1,20 @@
import React, { createElement, ComponentFactory, Fragment } from 'react';
-import {
- CustomElement,
- getDocument,
- getAttributeObject,
- selfClosingTags,
- getPropKey
-} from '@component-elements/shared';
+import { CustomElement, parseHtml, selfClosingTags, getPropKey } from '@component-elements/shared';
/* -----------------------------------
*
- * parseHtml
+ * parseChildren
*
* -------------------------------- */
-function parseHtml(this: CustomElement): ComponentFactory<{}, any> {
- const dom = getDocument(this.innerHTML);
+function parseChildren(this: CustomElement): ComponentFactory<{}, any> {
+ const children = parseHtml(this.innerHTML);
- if (!dom) {
+ if (!children.length) {
return void 0;
}
- const result = convertToVDom.call(this, dom);
+ const result = convertToVDom.call(this, children);
return () => result;
}
@@ -31,27 +25,21 @@ function parseHtml(this: CustomElement): ComponentFactory<{}, any> {
*
* -------------------------------- */
-function convertToVDom(this: CustomElement, node: Element) {
- if (node.nodeType === 3) {
- return node.textContent?.trim() || '';
+function convertToVDom(this: CustomElement, [nodeName, {slot, ...props}, children]: any) {
+ if(typeof children === 'string') {
+ return children.trim();
}
- if (node.nodeType !== 1) {
+ if(nodeName === void 0) {
return null;
}
- const nodeName = String(node.nodeName).toLowerCase();
- const childNodes = Array.from(node.childNodes);
+ const childNodes = () => children.map((child) =>
+ child.length ? convertToVDom.call(this, child) : void 0
+ );
- const children = () => childNodes.map((child) => convertToVDom.call(this, child));
- const { slot, ...props } = getAttributeObject(node.attributes);
-
- if (nodeName === 'script') {
- return null;
- }
-
- if (nodeName === 'body') {
- return createElement(Fragment, {}, children());
+ if (nodeName === null) {
+ return createElement(Fragment, {}, childNodes());
}
if (selfClosingTags.includes(nodeName)) {
@@ -59,12 +47,12 @@ function convertToVDom(this: CustomElement, node: Element) {
}
if (slot) {
- this.__slots[getPropKey(slot)] = getSlotChildren(children());
+ this.__slots[getPropKey(slot)] = getSlotChildren(childNodes());
return null;
}
- return createElement(nodeName, { ...props, key: Math.random() }, children());
+ return createElement(nodeName, props, childNodes());
}
/* -----------------------------------
@@ -89,4 +77,4 @@ function getSlotChildren(children: JSX.Element[]) {
*
* -------------------------------- */
-export { parseHtml };
+export { parseChildren };
diff --git a/packages/reactement/tests/parse.spec.ts b/packages/reactement/tests/parse.spec.ts
index 337e514..5dba788 100644
--- a/packages/reactement/tests/parse.spec.ts
+++ b/packages/reactement/tests/parse.spec.ts
@@ -1,6 +1,6 @@
import React, { createElement } from 'react';
import { mount } from 'enzyme';
-import { parseHtml } from '../src/parse';
+import { parseChildren } from '../src/parse';
/* -----------------------------------
*
@@ -24,21 +24,21 @@ describe('parse', () => {
describe('parseHtml()', () => {
it('should correctly handle misformed html', () => {
const testText = 'testText';
- const result = parseHtml.call({ innerHTML: `${testText}` });
+ const result = parseChildren.call({ innerHTML: `${testText}` });
const instance = mount(createElement(result, {}) as any);
expect(instance.html()).toEqual(`${testText}
`);
});
it('handles text values witin custom element', () => {
- const result = parseHtml.call({ innerHTML: testHeading });
+ const result = parseChildren.call({ innerHTML: testHeading });
const instance = mount(createElement(result, {}) as any);
expect(instance.text()).toEqual(testHeading);
});
it('handles whitespace within custom element', () => {
- const result = parseHtml.call({ innerHTML: testWhitespace });
+ const result = parseChildren.call({ innerHTML: testWhitespace });
const instance = mount(createElement(result, {}) as any);
expect(instance.text()).toEqual('');
@@ -46,14 +46,14 @@ describe('parse', () => {
});
it('removes script blocks for security', () => {
- const result = parseHtml.call({ innerHTML: testScript });
+ const result = parseChildren.call({ innerHTML: testScript });
const instance = mount(createElement(result, {}) as any);
expect(instance.text()).toEqual('');
});
it('correctly converts an HTML string into a VDom tree', () => {
- const result = parseHtml.call({ innerHTML: testHtml });
+ const result = parseChildren.call({ innerHTML: testHtml });
const instance = mount(createElement(result, {}) as any);
expect(instance.find('h1').text()).toEqual(testHeading);
@@ -68,7 +68,7 @@ describe('parse', () => {
const headingHtml = `${testHeading}
`;
const testHtml = `${headingHtml}${slotHtml}`;
- const result = parseHtml.call({ innerHTML: testHtml, __slots: slots });
+ const result = parseChildren.call({ innerHTML: testHtml, __slots: slots });
const instance = mount(createElement(result, {}) as any);
expect(instance.html()).toEqual(``);
diff --git a/packages/shared/jest.config.js b/packages/shared/jest.config.js
index be674ab..65f9584 100644
--- a/packages/shared/jest.config.js
+++ b/packages/shared/jest.config.js
@@ -14,10 +14,10 @@ module.exports = {
coveragePathIgnorePatterns: ['/node_modules/', '(.*).d.ts'],
coverageThreshold: {
global: {
- statements: 84,
- branches: 73,
- functions: 80,
- lines: 82,
+ statements: 91,
+ branches: 69,
+ functions: 94,
+ lines: 90,
},
},
transform: {
diff --git a/packages/shared/src/parse.ts b/packages/shared/src/parse.ts
index 4932cf5..c2e8ff6 100644
--- a/packages/shared/src/parse.ts
+++ b/packages/shared/src/parse.ts
@@ -1,4 +1,4 @@
-import { IProps, CustomElement, ErrorTypes } from './model';
+import { IProps, CustomElement, ErrorTypes, selfClosingTags } from './model';
/* -----------------------------------
*
@@ -25,6 +25,22 @@ function parseJson(this: CustomElement, value: string) {
return result;
}
+/* -----------------------------------
+ *
+ * parseHtml
+ *
+ * -------------------------------- */
+
+function parseHtml(htmlString: string) {
+ const dom = getDocument(htmlString);
+
+ if (!dom) {
+ return void 0;
+ }
+
+ return domToArray(dom);
+}
+
/* -----------------------------------
*
* getDocument
@@ -49,6 +65,42 @@ function getDocument(html: string) {
return nodes.body;
}
+/* -----------------------------------
+ *
+ * domToArray
+ *
+ * -------------------------------- */
+
+function domToArray(node: Element) {
+ if(node.nodeType === 3) {
+ return [null, {}, node.textContent?.trim() || ''];
+ }
+
+ const nodeName = String(node.nodeName).toLowerCase();
+ const childNodes = Array.from(node.childNodes);
+
+ if (nodeName === 'script' || node.nodeType !== 1) {
+ return [];
+ }
+
+ const children = () => childNodes.map((child: Element) => domToArray(child));
+ const props = getAttributeObject(node.attributes);
+
+ if (nodeName === 'script') {
+ return [];
+ }
+
+ if (nodeName === 'body') {
+ return [null, {}, children()];
+ }
+
+ if (selfClosingTags.includes(nodeName)) {
+ return [nodeName, props, []];
+ }
+
+ return [nodeName, props, children()];
+}
+
/* -----------------------------------
*
* getAttributeObject
@@ -114,4 +166,4 @@ function getPropKey(value: string) {
*
* -------------------------------- */
-export { parseJson, getDocument, getPropKey, getAttributeObject, getAttributeProps };
+export { parseJson, parseHtml, getDocument, getPropKey, getAttributeObject, getAttributeProps };
diff --git a/packages/shared/tests/parse.spec.ts b/packages/shared/tests/parse.spec.ts
index 491e3ff..fdb9074 100644
--- a/packages/shared/tests/parse.spec.ts
+++ b/packages/shared/tests/parse.spec.ts
@@ -1,6 +1,6 @@
import { h } from 'preact';
import { mount } from 'enzyme';
-import { parseJson, getPropKey } from '../src/parse';
+import { parseJson, parseHtml, getPropKey } from '../src/parse';
/* -----------------------------------
*
@@ -11,6 +11,7 @@ import { parseJson, getPropKey } from '../src/parse';
const testHeading = 'testHeading';
const testData = { testHeading };
const testJson = JSON.stringify(testData);
+const testHtml = `${testHeading}
Hello there
`;
/* -----------------------------------
*
@@ -52,6 +53,23 @@ describe('parse', () => {
});
});
+ describe('parseHtml', () => {
+ it('correctly converts a DOM structure to multidimensional array', () => {
+ const result = parseHtml(testHtml);
+
+ expect(result).toEqual([null, {}, [
+ ['h1', {}, [[null, {}, testHeading]]],
+ ['br', {}, []],
+ ['div', {}, [
+ ['h2', { title: 'Main Title' }, [
+ [null, {}, 'Hello'],
+ ['em', {}, [[null, {}, 'there']],
+ ]]],
+ ]]
+ ]]);
+ });
+ });
+
describe('getPropKey', () => {
const testCamel = 'testSlot';
const testKebab = 'test-slot';