Skip to content
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
27 changes: 26 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ permissions:
contents: read

jobs:
test:
unit:
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -34,3 +34,28 @@ jobs:

- name: Run integration tests
run: npm run test:integration

integration:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chromium, firefox, webkit]

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Use Node.js
uses: actions/setup-node@v6
with:
cache: npm
node-version-file: .nvmrc

- name: Install dependencies
run: npm ci --prefer-offline

- name: Install Playwright browsers
run: npx playwright install ${{ matrix.browser == 'webkit' && '--with-deps' || '' }} ${{ matrix.browser }}

- name: Run browser tests on ${{ matrix.browser }}
run: npm test -- --config=vitest.config.browser.mts --browser=${{ matrix.browser }}
19 changes: 0 additions & 19 deletions __tests__/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -340,22 +340,3 @@ exports[`replace option > replaces the element if a valid React element is retur
</body>
</html>
`;

exports[`trim option > preserves whitespace text nodes when disabled if valid in parent (default) 1`] = `
<table>
<tbody>
<tr>
<td>
hello
</td>
<td>


</td>
<td>

</td>
</tr>
</tbody>
</table>
`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import attributesToProps, { type Attributes } from '../src/attributes-to-props';
import * as utilities from '../src/utilities';
import attributesToProps, {
type Attributes,
} from '../../src/attributes-to-props';

it('returns empty object is argument is undefined', () => {
expect(attributesToProps()).toEqual({});
Expand Down Expand Up @@ -353,24 +354,3 @@ describe('attributesToProps with custom attribute', () => {
`);
});
});

describe('utilities.PRESERVE_CUSTOM_ATTRIBUTES=false', () => {
const emptyProps = {};

beforeAll(() => {
vi.spyOn(utilities, 'PRESERVE_CUSTOM_ATTRIBUTES', 'get').mockReturnValue(
false,
);
});

afterAll(() => {
vi.restoreAllMocks();
});

it('omits unknown attributes', () => {
const attributes = {
unknownAttribute: 'someValue',
};
expect(attributesToProps(attributes)).toEqual(emptyProps);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import attributesToProps from '../../src/attributes-to-props';

vi.mock('../../src/utilities', async (importOriginal) => ({
...(await importOriginal()),
PRESERVE_CUSTOM_ATTRIBUTES: false,
}));

describe('utilities.PRESERVE_CUSTOM_ATTRIBUTES=false', () => {
const emptyProps = {};

it('omits unknown attributes', () => {
const attributes = { unknownAttribute: 'someValue' };
expect(attributesToProps(attributes)).toEqual(emptyProps);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`domToReact > when React version is 15 > removes unknown attributes 1`] = `
<custom-element
className="myClass"
style={
{
"OTransition": "all .5s",
"lineHeight": "1",
}
}
/>
`;
125 changes: 125 additions & 0 deletions __tests__/dom-to-react/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`domToReact > converts <textarea> correctly 1`] = `
<textarea
defaultValue="foo"
/>
`;

exports[`domToReact > converts LaTeX 1`] = `
[
<span
className="math"
>
\\left(\\right)\\rD\\rightarrow\\reals\\ni
</span>,
"
",
]
`;

exports[`domToReact > converts SVG element with viewBox attribute 1`] = `
<svg
id="foo"
viewBox="0 0 512 512"
>
Inner
</svg>
`;

exports[`domToReact > converts custom element with attributes 1`] = `
<custom-element
class="myClass"
custom-attribute="value"
style={
{
"OTransition": "all .5s",
"lineHeight": "1",
}
}
/>
`;

exports[`domToReact > converts multiple DOM nodes to React 1`] = `
[
<p>
foo
</p>,
<p>
bar
</p>,
]
`;

exports[`domToReact > converts single DOM node to React 1`] = `
<p>
foo
</p>
`;

exports[`domToReact > does not escape <script> content 1`] = `
<script
dangerouslySetInnerHTML={
{
"__html": "alert(1 < 2);",
}
}
/>
`;

exports[`domToReact > does not escape <style> content 1`] = `
<style
dangerouslySetInnerHTML={
{
"__html": "body > .foo { color: #f00; }",
}
}
/>
`;

exports[`domToReact > skips doctype and comments 1`] = `
[
<p>
foo
</p>,
<p>
foo
</p>,
]
`;

exports[`domToReact > when React >=16 > preserves unknown attributes 1`] = `
<custom-element
class="myClass"
custom-attribute="value"
style={
{
"OTransition": "all .5s",
"lineHeight": "1",
}
}
/>
`;

exports[`transform option > can wrap all elements 1`] = `
<div>
<ol>
<div>
<li>
<div>
One
</div>
</li>
</div>
<div>
<li
value="2"
>
<div>
Two
</div>
</li>
</div>
</ol>
</div>
`;
18 changes: 18 additions & 0 deletions __tests__/dom-to-react/custom-attributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import htmlToDOM from 'html-dom-parser';

import domToReact from '../../src/dom-to-react';
import { html } from '../data';

vi.mock('react', async (importOriginal) => ({
...(await importOriginal()),
version: '15.7.0',
}));

describe('domToReact', () => {
describe('when React version is 15', () => {
it('removes unknown attributes', () => {
const reactElement = domToReact(htmlToDOM(html.customElement));
expect(reactElement).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import htmlToDOM from 'html-dom-parser';
import type Preact from 'preact';
import type React from 'react';

import { type DOMNode, Element, type HTMLReactParserOptions } from '../src';
import domToReact from '../src/dom-to-react';
import * as utilities from '../src/utilities';
import { html, svg } from './data';
import { render } from './helpers';
import { type DOMNode, Element, type HTMLReactParserOptions } from '../../src';
import domToReact from '../../src/dom-to-react';
import { html, svg } from '../data';
import { render } from '../helpers';

describe('domToReact', () => {
it.each([
Expand Down Expand Up @@ -88,33 +89,40 @@ describe('domToReact', () => {
});
});

/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
describe('library option', () => {
const React = require('react');
const Preact = require('preact');
let react: typeof React;
let preact: typeof Preact;

beforeAll(async () => {
react = await import('react');
preact = await import('preact');
});

it('converts with React by default', () => {
const reactElement = domToReact(htmlToDOM(html.single));
expect(React.isValidElement(reactElement)).toBe(true);
expect(Preact.isValidElement(reactElement)).toBe(false);
expect(reactElement).toEqual(React.createElement('p', {}, 'foo'));
expect(react.isValidElement(reactElement)).toBe(true);
expect(preact.isValidElement(reactElement)).toBe(false);
expect(reactElement).toEqual(react.createElement('p', {}, 'foo'));
});

it('converts with Preact', () => {
const parsedElement = domToReact(htmlToDOM(html.single), {
library: Preact,
library: preact,
});
const preactElement = Preact.createElement('p', {}, 'foo');
expect(React.isValidElement(parsedElement)).toBe(false);
expect(Preact.isValidElement(parsedElement)).toBe(true);
const preactElement = preact.createElement('p', {}, 'foo');

expect(react.isValidElement(parsedElement)).toBe(false);
expect(preact.isValidElement(parsedElement)).toBe(true);

// remove `__v` key since it's causing test equality to fail
// @ts-expect-error Property '__v' does not exist on type 'string'.
delete parsedElement.__v;
// @ts-expect-error Property '__v' does not exist on type 'VNode<ClassAttributes<HTMLElement>>'.
delete preactElement.__v;

expect(parsedElement).toEqual(preactElement);
});
});
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */

describe('replace option', () => {
it.each([undefined, null, 0, 1, true, false, {}])(
Expand Down Expand Up @@ -209,21 +217,4 @@ describe('domToReact', () => {
expect(reactElement).toMatchSnapshot();
});
});

describe('when React <16', () => {
beforeAll(() => {
vi.spyOn(utilities, 'PRESERVE_CUSTOM_ATTRIBUTES', 'get').mockReturnValue(
false,
);
});

afterAll(() => {
vi.restoreAllMocks();
});

it('removes unknown attributes', () => {
const reactElement = domToReact(htmlToDOM(html.customElement));
expect(reactElement).toMatchSnapshot();
});
});
});
Loading