import { describe, it, expect } from 'vitest'; import { h, mount, Fragment } from 'lithent'; import { renderToString } from '../renderToString'; // ── helpers ── /** Render a no-arg component to string */ function render(Comp: ReturnType): string { return renderToString(h(Comp, {})); } // ── Text node HTML escaping ── describe('renderToString — text node HTML escaping', () => { it('escapes < and > in text node content', () => { const Comp = mount(_r => () => (

{'Page shell with anchors'}

)); expect(render(Comp)).toBe('

Page shell with <h-state> anchors

'); }); it('escapes & in text node content', () => { const Comp = mount(_r => () =>

{'foo & bar'}

); expect(render(Comp)).toBe('

foo & bar

'); }); it('escapes multiple occurrences', () => { const Comp = mount(_r => () =>

{'bold & italic'}

); expect(render(Comp)).toBe( '

<b>bold</b> & <i>italic</i>

' ); }); it('does not escape plain text', () => { const Comp = mount(_r => () =>

{'hello world'}

); expect(render(Comp)).toBe('

hello world

'); }); it('does not escape innerHTML prop (raw HTML passthrough)', () => { const Comp = mount(_r => () => (
)); expect(render(Comp)).toBe( '
raw content
' ); }); it('escapes text nodes inside nested elements', () => { const Comp = mount(_r => () => (
{''}
)); expect(render(Comp)).toBe( '
<script>alert(1)</script>
' ); }); it('escapes text nodes mixed with other children', () => { const Comp = mount(_r => () => (

{'prefix '} bold {' suffix'}

)); expect(render(Comp)).toBe( '

prefix <tag> bold suffix

' ); }); it('escapes text nodes inside Fragment', () => { const Comp = mount(_r => () => (

{' anchor'}

{'A & B'}

)); expect(render(Comp)).toBe('

<h-state> anchor

A & B

'); }); }); // ── Attribute value escaping ── describe('renderToString — attribute escaping', () => { it('escapes " in an attribute value so it cannot close the attribute', () => { const value = '">'; const Comp = mount(_r => () => ); expect(render(Comp)).toBe( '' ); }); it('escapes & in an attribute value', () => { const Comp = mount(_r => () => go); expect(render(Comp)).toBe('go'); }); it('escapes " in a style value', () => { const Comp = mount(_r => () => (
)); expect(render(Comp)).toBe( '
' ); }); it('does not change a plain attribute value', () => { const Comp = mount(_r => () =>
); expect(render(Comp)).toBe('
'); }); it('drops a prop whose name could not be an attribute name', () => { const props = { 'x onmouseover="alert(1)"': 'y', id: 'ok' }; const Comp = mount(_r => () =>
); expect(render(Comp)).toBe('
'); }); it('round-trips through the HTML parser', () => { const value = `a"b'ce&f`; const Comp = mount(_r => () =>
); const wrap = document.createElement('div'); wrap.innerHTML = render(Comp); expect(wrap.children.length).toBe(1); expect(wrap.firstElementChild!.getAttribute('title')).toBe(value); }); });