Skip to content

refactor: improve parser types #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 3, 2020
Merged
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
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"node": true
},
"extends": "eslint:recommended",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
Expand Down
18 changes: 13 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@ import domToReact from './lib/dom-to-react';
import htmlToDOM from 'html-dom-parser';

export interface HTMLReactParserOptions {
// TODO: Replace `object` by type for objects like `{ type: 'h1', props: { children: 'Heading' } }`
replace?: (
domNode: DomElement
) => JSX.Element | object | void | undefined | null | false;
library?: object;
library?: {
cloneElement: (
element: JSX.Element,
props?: object,
...children: any
) => JSX.Element;
createElement: (type: any, props?: object, ...children: any) => JSX.Element;
isValidElement: (element: any) => boolean;
[key: string]: any;
};
}

/**
* Converts HTML string to JSX element(s).
*
* @param html - The HTML string to parse to JSX element(s).
* @param options - The parser options.
* @return - Single or array of JSX elements.
* @param html - HTML string to parse to JSX element(s).
* @param options - Parser options.
* @return - JSX element(s).
*/
declare function HTMLReactParser(
html: string,
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function HTMLReactParser(html, options) {
if (typeof html !== 'string') {
throw new TypeError('First argument must be a string');
}
if (html === '') {
return [];
}
return domToReact(htmlToDOM(html, domParserOptions), options);
}

Expand Down
6 changes: 3 additions & 3 deletions lib/dom-to-react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { DomElement } from 'domhandler';
/**
* Converts DOM nodes to JSX element(s).
*
* @param nodes - An array of DomNodes to convert to JSX element(s).
* @param options - Options to use when converting to JSX.
* @returns Single or array of JSX elements.
* @param nodes - DOM nodes.
* @param options - Parser options.
* @returns - JSX element(s).
*/
export default function domToReact(
nodes: DomElement[],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@rollup/plugin-commonjs": "^12.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"@types/react": "^16.9.35",
"@typescript-eslint/parser": "^3.1.0",
"benchmark": "^2.1.4",
"dtslint": "^3.6.10",
"eslint": "^7.1.0",
Expand Down
2 changes: 1 addition & 1 deletion test/attributes-to-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const assert = require('assert');
const attributesToProps = require('../lib/attributes-to-props');
const utilities = require('../lib/utilities');

describe('attributes-to-props', () => {
describe('attributes to props', () => {
describe('HTML', () => {
it('converts attributes to React props', () => {
assert.deepEqual(
Expand Down
2 changes: 1 addition & 1 deletion test/dom-to-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const domToReact = require('../lib/dom-to-react');
const { data, render } = require('./helpers/');
const utilities = require('../lib/utilities');

describe('dom-to-react', () => {
describe('DOM to React', () => {
it('converts single DOM node to React', () => {
const html = data.html.single;
const reactElement = domToReact(htmlToDOM(html));
Expand Down
8 changes: 6 additions & 2 deletions test/html-to-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const React = require('react');
const parse = require('..');
const { data, render } = require('./helpers/');

describe('html-to-react', () => {
describe('HTML to React', () => {
describe('exports', () => {
it('has default ES Module', () => {
assert.strictEqual(parse.default, parse);
Expand All @@ -13,7 +13,7 @@ describe('html-to-react', () => {
assert.strictEqual(parse.domToReact, require('../lib/dom-to-react'));
});

it('contains htmlToDOM', () => {
it('has htmlToDOM', () => {
assert.strictEqual(parse.htmlToDOM, require('html-dom-parser'));
});
});
Expand All @@ -27,6 +27,10 @@ describe('html-to-react', () => {
});
});

it('converts empty string to empty array', () => {
assert.deepEqual(parse(''), []);
});

it('returns string if it cannot be parsed as HTML', () => {
assert.strictEqual(parse('foo'), 'foo');
});
Expand Down
66 changes: 45 additions & 21 deletions test/types/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,67 @@
import parse, { HTMLReactParserOptions, domToReact, htmlToDOM } from 'html-react-parser';
import parse, {
HTMLReactParserOptions,
domToReact,
htmlToDOM
} from 'html-react-parser';
import * as React from 'react';

/* $ExpectType ReactElement | ReactElement[] */
parse('<div>text</div>');
// $ExpectError
parse();

// `options.replace`
// $ExpectType Element | Element[]
parse('');

// Return React.createElement from `replace`
parse('<p id="replace">text</p>', {
// $ExpectType Element | Element[]
parse('string');

// $ExpectType Element | Element[]
parse('<p>text</p>');

// $ExpectType Element | Element[]
parse('<li>1</li><li>2</li>');

// $ExpectType Element | Element[]
parse('<br id="replace">', {
replace: domNode => {
if (domNode.attribs && domNode.attribs.id === 'replace') {
return React.createElement('span', {}, 'replaced');
return <span>replaced</span>;
}
}
});

// Return ReactElement
const options: HTMLReactParserOptions = {
// $ExpectType Element | Element[]
parse('<br id="remove">', {
replace({ attribs }) {
return attribs && attribs.id === 'remove' && <React.Fragment />;
return attribs && attribs.id === 'remove' && <></>;
}
};
});

parse('<p><br id="remove"></p>', options);
let options: HTMLReactParserOptions;

// Return domhandler node
parse('<a id="header" href="#">Heading</a>', {
options = {
replace: node => {
if (node.attribs && node.attribs.id === 'header') {
return {
type: 'h1',
props: { children: 'Heading' }
};
return;
}
}
};

// $ExpectType Element | Element[]
parse('<a id="header" href="#">Heading</a>', options);

// $ExpectType Element | Element[]
parse('<hr>', {
library: {
cloneElement: (element, props, children) =>
React.cloneElement(element, props, children),
createElement: (type, props, children) =>
React.createElement(type, props, children),
isValidElement: element => React.isValidElement(element)
}
});

// $ExpectType DomElement[]
const dom = htmlToDOM('<div>text</div>');
const domNodes = htmlToDOM('<div>text</div>');

/* $ExpectType ReactElement | ReactElement[] */
domToReact(dom);
// $ExpectType Element | Element[]
domToReact(domNodes);
27 changes: 14 additions & 13 deletions test/types/lib/dom-to-react.test.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
import { DomElement } from 'domhandler';
import { HTMLReactParserOptions } from 'html-react-parser';
import domToReact from 'html-react-parser/lib/dom-to-react';
import * as React from 'react';
import htmlToDOM from 'html-dom-parser';

/* $ExpectType ReactElement | ReactElement[] */
domToReact(htmlToDOM('<div>text</div>'));
// $ExpectType DomElement[]
htmlToDOM('<div>text</div>');

// `options.replace`
// $ExpectType Element | Element[]
domToReact(htmlToDOM('<div>text</div>'));

// Return React.createElement from `replace`
// $ExpectType Element | Element[]
domToReact(htmlToDOM('<p id="replace">text</p>'), {
replace: domNode => {
if (domNode.attribs && domNode.attribs.id === 'replace') {
return React.createElement('span', {}, 'replaced');
return <span>replaced</span>;
}
}
});

// Return ReactElement
const options: HTMLReactParserOptions = {
let options: HTMLReactParserOptions;

options = {
replace({ attribs }) {
return attribs && attribs.id === 'remove' && <React.Fragment />;
return attribs && attribs.id === 'remove' && <></>;
}
};

// $ExpectType Element | Element[]
domToReact(htmlToDOM('<p><br id="remove"></p>'), options);

// Return domhandler node
// $ExpectType Element | Element[]
domToReact(htmlToDOM('<a id="header" href="#">Heading</a>'), {
replace: node => {
if (node.attribs && node.attribs.id === 'header') {
return {
type: 'h1',
props: { children: 'Heading' }
};
return;
}
}
});