You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/words/engine/serialize-html-v2.test.ts

130 lines
3.6 KiB

/**
* R3B — serialize-html-v2 suite.
*
* Verifies: HTML output is well-formed, escapes correctly, preserves
* inline styles from `block.visual.*`.
*/
import { describe, expect, it } from 'vitest';
import { serializeHtmlV2 } from './serialize-html-v2';
import { WORDS_DOCUMENT_VERSION_V2, type WordsDocumentV2 } from './types-v2';
function doc(...children: WordsDocumentV2['children']): WordsDocumentV2 {
return { version: WORDS_DOCUMENT_VERSION_V2, children };
}
describe('serializeHtmlV2', () => {
it('paragraph with plain text', () => {
const html = serializeHtmlV2(
doc({ type: 'paragraph', children: [{ type: 'text', text: 'hello' }] })
);
expect(html).toContain('<p ');
expect(html).toContain('data-words-block="paragraph"');
expect(html).toContain('>hello</p>');
});
it('escapes &, <, > in text content', () => {
const html = serializeHtmlV2(
doc({ type: 'paragraph', children: [{ type: 'text', text: '<script>&amp;</script>' }] })
);
expect(html).toContain('&lt;script&gt;&amp;amp;&lt;/script&gt;');
expect(html).not.toContain('<script>');
});
it('escapes " and & in attribute values', () => {
const html = serializeHtmlV2(
doc({
type: 'paragraph',
children: [
{
type: 'link',
href: 'https://example.com?a=1&b="2"',
children: [{ type: 'text', text: 'link' }]
}
]
})
);
expect(html).toContain('href="https://example.com?a=1&amp;b=&quot;2&quot;"');
});
it('emits <hr /> for divider (void element)', () => {
const html = serializeHtmlV2(doc({ type: 'divider' }));
expect(html).toContain('<hr ');
expect(html).toContain('/>');
expect(html).not.toContain('</hr>');
});
it('emits <img /> as void element', () => {
const html = serializeHtmlV2(
doc({ type: 'image', src: 'https://x/cat.png', alt: 'cat' })
);
expect(html).toContain('<img src="https://x/cat.png"');
expect(html).toContain('alt="cat"');
expect(html).toContain('/>');
});
it('preserves inline styles from visual', () => {
const html = serializeHtmlV2(
doc({
type: 'paragraph',
children: [{ type: 'text', text: 'x' }],
visual: { background: '#fafafa', padding: 12 }
})
);
// Canonical visualToStyle order: padding (layout) → background (fill)
expect(html).toContain('style="padding:12px;background-color:#fafafa"');
});
it('color mark produces <span style="...">', () => {
const html = serializeHtmlV2(
doc({
type: 'paragraph',
children: [
{ type: 'text', text: 'red text', marks: [{ type: 'color', value: '#ff0000' }] }
]
})
);
expect(html).toContain('<span style="color:#ff0000">red text</span>');
});
it('callout emits <div role="note">', () => {
const html = serializeHtmlV2(
doc({
type: 'callout',
intent: 'risk',
children: [{ type: 'paragraph', children: [{ type: 'text', text: 'warning' }] }]
})
);
expect(html).toContain('role="note"');
expect(html).toContain('data-words-callout-intent="risk"');
});
it('pretty mode adds newlines and indentation', () => {
const html = serializeHtmlV2(
doc({
type: 'callout',
intent: 'neutral',
children: [{ type: 'paragraph', children: [{ type: 'text', text: 'x' }] }]
}),
{ pretty: true }
);
expect(html).toContain('\n');
expect(html).toContain(' <p');
});
it('table with header row emits <th> + <td>', () => {
const html = serializeHtmlV2(
doc({
type: 'table',
headerRow: true,
rows: [
{ cells: [{ children: [{ type: 'text', text: 'h1' }] }] },
{ cells: [{ children: [{ type: 'text', text: 'a' }] }] }
]
})
);
expect(html).toMatch(/<th [^>]*>h1<\/th>/);
expect(html).toMatch(/<td [^>]*>a<\/td>/);
});
});

Powered by TurnKey Linux.