import { describe, expect, test } from 'bun:test';
import { HtmlConverter } from '../lib/markdown/html-converter.js';
describe('HtmlConverter', () => {
test('converts simple markdown to HTML', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('Hello **world**!');
expect(html).toContain('world');
});
test('converts headings', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('# Heading 1\n## Heading 2');
expect(html).toContain('
Heading 1
');
expect(html).toContain('Heading 2
');
});
test('converts lists', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('- Item 1\n- Item 2');
expect(html).toContain('');
expect(html).toContain('- ');
expect(html).toContain('Item 1');
});
test('converts inline formatting inside list items', () => {
const converter = new HtmlConverter();
const markdown =
'- **[ger](https://github.com/aaronshaf/ger)** - Gerrit CLI for code review\n- **[jk](https://github.com/aaronshaf/jk)** - Jenkins CLI';
const { html } = converter.convert(markdown);
expect(html).toContain('
');
expect(html).toContain('ger');
expect(html).toContain('jk');
expect(html).toContain('Gerrit CLI for code review');
expect(html).not.toContain('**[ger]'); // Should not contain raw markdown
});
test('converts italic and strikethrough', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('Text with *italic* and ~~strikethrough~~ formatting.');
expect(html).toContain('italic');
expect(html).toContain('strikethrough');
});
test('converts line breaks and horizontal rules', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('Line 1 \nLine 2\n\n---\n\nLine 3');
expect(html).toContain('
');
expect(html).toContain('
');
});
test('converts links', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('[Example](https://example.com)');
expect(html).toContain('Example');
});
test('converts code blocks to Confluence macro', () => {
const converter = new HtmlConverter();
const markdown = '```javascript\nconst x = 1;\n```';
const { html } = converter.convert(markdown);
expect(html).toContain('ac:structured-macro');
expect(html).toContain('ac:name="code"');
expect(html).toContain('javascript');
expect(html).toContain('const x = 1;');
});
test('converts tables', () => {
const converter = new HtmlConverter();
const markdown = '| Col 1 | Col 2 |\n|-------|-------|\n| A | B |';
const { html } = converter.convert(markdown);
expect(html).toContain('');
expect(html).toContain('| Col 1 | ');
expect(html).toContain('A | ');
});
test('converts blockquotes', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('> This is a quote');
expect(html).toContain('');
expect(html).toContain('This is a quote');
});
test('converts info panel syntax to Confluence macro', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('> **Info:** This is an info message');
expect(html).toContain('ac:structured-macro');
expect(html).toContain('ac:name="info"');
expect(html).toContain('This is an info message');
});
test('ensures XHTML compliance with self-closing tags', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('Line 1\n\nLine 2\n\n---');
expect(html).toContain('
');
expect(html).not.toContain('
');
});
test('warns about local image references', () => {
const converter = new HtmlConverter();
const { warnings } = converter.convert('');
expect(warnings.length).toBeGreaterThan(0);
expect(warnings.some((w) => w.includes('local-image.png'))).toBe(true);
});
test('strips @ prefix from mentions', () => {
const converter = new HtmlConverter();
const { html, warnings } = converter.convert('Contact @user123 for help.');
// @ should be stripped, leaving just the username
expect(html).toContain('user123');
expect(html).not.toContain('@user123');
// No warning since we handle it by stripping
expect(warnings.some((w) => w.includes('mentions'))).toBe(false);
});
test('does not strip @ from email addresses', () => {
const converter = new HtmlConverter();
const { html } = converter.convert('Email us at support@example.com for help.');
// Email addresses should be preserved
expect(html).toContain('support@example.com');
});
test('warns about task lists', () => {
const converter = new HtmlConverter();
const { warnings } = converter.convert('- [x] Done\n- [ ] Todo');
expect(warnings.length).toBeGreaterThan(0);
expect(warnings.some((w) => w.includes('Task list'))).toBe(true);
});
test('escapes CDATA sections in code blocks', () => {
const converter = new HtmlConverter();
const markdown = '```javascript\nconst x = "]]>";\n```';
const { html } = converter.convert(markdown);
// Should escape ]]> to prevent breaking CDATA
expect(html).toContain(']]]]>');
expect(html).not.toContain('const x = "]]>";');
});
test('sanitizes language identifiers in code blocks', () => {
const converter = new HtmlConverter();
const markdown = '```javascript">\n\nworld\n
';
const { html, warnings } = converter.convert(markdown);
// Should remove script tags
expect(html).not.toContain('