/* * SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { renderToStaticMarkup } from 'react-dom/server' import { convertHtmlToReact, ParserOptions } from './convertHtmlToReact.js' import { convertNodeToReactElement } from './convertNodeToReactElement.js' import { Document, isTag, isText } from 'domhandler' import { NodeToReactElementTransformer } from './NodeToReactElementTransformer.js' import React, { ReactElement } from 'react' import { beforeEach, describe, expect, it, vitest, beforeAll, afterAll } from 'vitest' import { ElementType } from 'htmlparser2' function parseHtmlToReactHtml(html: string, options: ParserOptions = {}) { return renderToStaticMarkup(
{convertHtmlToReact(html, options)}
) } function expectedHtml(html: string) { return `
${html}
` } describe('Integration tests', () => { it('should render a simple element', () => { const markup = '
test
' expect(parseHtmlToReactHtml(markup)).toBe(expectedHtml(markup)) }) it('should render multiple sibling elements', () => { const markup = '
test1
test2' expect(parseHtmlToReactHtml(markup)).toBe(expectedHtml(markup)) }) it('should render nested elements', () => { const markup = '
test1
' expect(parseHtmlToReactHtml(markup)).toBe(expectedHtml(markup)) }) it('should handle bad html', () => { expect( parseHtmlToReactHtml( '
testtest
' ) ).toBe( expectedHtml( '
testtest
' ) ) }) it('should ignore doctypes', () => { expect(parseHtmlToReactHtml('
test
')).toBe( expectedHtml('
test
') ) }) it('should ignore comments', () => { expect( parseHtmlToReactHtml('
test1
test2
') ).toBe(parseHtmlToReactHtml('
test1
test2
')) }) it('should ignore script tags', () => { expect(parseHtmlToReactHtml('')).toBe( expectedHtml('') ) }) it('should ignore event handlers', () => { expect( parseHtmlToReactHtml('test') ).toBe(expectedHtml('test')) }) it('should handle attributes', () => { const markup = '
test
' expect(parseHtmlToReactHtml(markup)).toBe(expectedHtml(markup)) }) it('should handle inline styles', () => { const markup = '
test
' expect(parseHtmlToReactHtml(markup)).toBe(expectedHtml(markup)) }) it('should ignore inline styles that are empty strings', () => { expect(parseHtmlToReactHtml('
test
')).toBe( expectedHtml('
test
') ) }) it('should not allow nesting of void elements', () => { expect(parseHtmlToReactHtml('

test

')).toBe( expectedHtml('

test

') ) }) it('should convert boolean attribute values', () => { expect(parseHtmlToReactHtml('')).toBe( expectedHtml('') ) expect(parseHtmlToReactHtml('')).toBe( expectedHtml('') ) expect(parseHtmlToReactHtml('')).toBe( expectedHtml('') ) }) ;[ ['CONTENTEDITABLE', 'contentEditable'], ['LABEL', 'label'], ['iTemREF', 'itemRef'] ].forEach(([attr, prop]) => { it(`should convert attribute ${attr} to prop ${prop}`, () => { const nodes = convertHtmlToReact(`
`, {}) expect(nodes).toHaveLength(1) expect((nodes[0] as ReactElement).props).toHaveProperty(prop) }) }) it('should decode html entities by default', () => { expect(parseHtmlToReactHtml('!')).toBe( expectedHtml('!') ) }) it('should not decode html entities when the option is disabled', () => { expect( parseHtmlToReactHtml('!', { decodeEntities: false }) ).toBe(expectedHtml('!')) }) describe('transform function', () => { it('should use the response when it is not undefined', () => { expect( parseHtmlToReactHtml('test
another
', { transform(node, index) { return

transformed

} }) ).toBe(expectedHtml('

transformed

transformed

')) }) it('should not render elements and children when returning null', () => { expect( parseHtmlToReactHtml( '

testinner testbold child

', { transform(node) { if ( isTag(node) && ElementType.isTag(node) && node.name === 'span' ) { return null } } } ) ).toBe(expectedHtml('

test

')) }) it('should allow modifying nodes', () => { expect( parseHtmlToReactHtml('test link', { transform(node, index) { if (isTag(node)) { node.attribs.href = '/changed' } return convertNodeToReactElement(node, index) } }) ).toBe(expectedHtml('test link')) }) it('should allow passing the transform function down to children', () => { const transform: NodeToReactElementTransformer = (node, index) => { if (isTag(node)) { if (node.name === 'ul') { node.attribs.class = 'test' return convertNodeToReactElement(node, index, transform) } } else if (isText(node)) { return node.data.replace(/list/, 'changed') } else { return null } } expect( parseHtmlToReactHtml('', { transform }) ).toBe( expectedHtml( '' ) ) }) }) it('should not render invalid tags', () => { expect(parseHtmlToReactHtml('
test')).toBe( expectedHtml('
test
') ) }) it('should not render invalid attributes', () => { expect( parseHtmlToReactHtml('
content
') ).toBe(expectedHtml('
content
')) }) it('should preprocess nodes correctly', () => { expect( parseHtmlToReactHtml('
preprocess test
', { preprocessNodes(document) { return new Document([...document.childNodes, ...document.childNodes]) } }) ).toBe(expectedHtml('
preprocess test
preprocess test
')) }) })