Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/preactement/src/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
getElementAttributes,
getAsyncComponent,
} from '@component-elements/shared';
import { parseHtml } from './parse';
import { parseChildren } from './parse';
import { IOptions, ComponentFunction } from './model';

/* -----------------------------------
Expand Down Expand Up @@ -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 };
Expand Down
48 changes: 18 additions & 30 deletions packages/preactement/src/parse.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -31,40 +25,34 @@ 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)) {
return h(nodeName, props);
}

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());
}

/* -----------------------------------
Expand All @@ -89,4 +77,4 @@ function getSlotChildren(children: JSX.Element[]) {
*
* -------------------------------- */

export { parseHtml };
export { parseChildren };
30 changes: 17 additions & 13 deletions packages/preactement/tests/parse.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

/* -----------------------------------
*
Expand All @@ -20,42 +21,45 @@ const testScript = `<script>alert('danger')</script>`;
* -------------------------------- */

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: `<h1>${testText}` });
const result = parseChildren.call({ innerHTML: `<h1>${testText}` });
const instance = mount(h(result, {}) as any);

expect(instance.html()).toEqual(`<h1>${testText}</h1>`);
});

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('');
expect(instance.html()).toEqual('');
});

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', () => {
Expand All @@ -69,7 +73,7 @@ describe('parse', () => {
const headingHtml = `<h1>${testHeading}</h1>`;
const testHtml = `<section>${headingHtml}${slotHtml}</section>`;

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(`<section>${headingHtml}</section>`);
Expand Down
4 changes: 2 additions & 2 deletions packages/reactement/src/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
getElementAttributes,
getAsyncComponent,
} from '@component-elements/shared';
import { parseHtml } from './parse';
import { parseChildren } from './parse';
import { IOptions, ComponentFunction } from './model';

/* -----------------------------------
Expand Down Expand Up @@ -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 };
Expand Down
48 changes: 18 additions & 30 deletions packages/reactement/src/parse.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -31,40 +25,34 @@ 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)) {
return createElement(nodeName, props);
}

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());
}

/* -----------------------------------
Expand All @@ -89,4 +77,4 @@ function getSlotChildren(children: JSX.Element[]) {
*
* -------------------------------- */

export { parseHtml };
export { parseChildren };
14 changes: 7 additions & 7 deletions packages/reactement/tests/parse.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createElement } from 'react';
import { mount } from 'enzyme';
import { parseHtml } from '../src/parse';
import { parseChildren } from '../src/parse';

/* -----------------------------------
*
Expand All @@ -24,36 +24,36 @@ describe('parse', () => {
describe('parseHtml()', () => {
it('should correctly handle misformed html', () => {
const testText = 'testText';
const result = parseHtml.call({ innerHTML: `<h1>${testText}` });
const result = parseChildren.call({ innerHTML: `<h1>${testText}` });
const instance = mount(createElement(result, {}) as any);

expect(instance.html()).toEqual(`<h1>${testText}</h1>`);
});

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('');
expect(instance.html()).toEqual('');
});

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);
Expand All @@ -68,7 +68,7 @@ describe('parse', () => {
const headingHtml = `<h1>${testHeading}</h1>`;
const testHtml = `<section>${headingHtml}${slotHtml}</section>`;

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(`<section>${headingHtml}</section>`);
Expand Down
8 changes: 4 additions & 4 deletions packages/shared/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading