import { describe, it, expect } from 'vitest'; import { convertHtmlToBlockNote, convertMarkdownToBlocks } from './contentConversion'; describe('convertHtmlToBlockNote', () => { it('should handle empty input', async () => { expect(await convertHtmlToBlockNote('')).toEqual([]); // @ts-ignore expect(await convertHtmlToBlockNote(null)).toEqual([]); // @ts-ignore expect(await convertHtmlToBlockNote(undefined)).toEqual([]); }); it('should convert simple paragraphs', async () => { const html = '

Hello World

'; const result = await convertHtmlToBlockNote(html); expect(result).toHaveLength(1); expect(result[0].type).toBe('paragraph'); const text = result[0].content?.find((c: any) => c.type === 'text'); expect(text?.text).toBe('Hello World'); }); it('should convert headings', async () => { const html = '

Title 1

Title 2

Title 3

'; const result = await convertHtmlToBlockNote(html); expect(result).toHaveLength(3); expect(result[0]).toMatchObject({ type: 'heading', props: expect.objectContaining({ level: 1 }) }); expect(result[1]).toMatchObject({ type: 'heading', props: expect.objectContaining({ level: 2 }) }); expect(result[2]).toMatchObject({ type: 'heading', props: expect.objectContaining({ level: 3 }) }); }); it('should convert lists', async () => { const html = `
  1. Ordered 1
  2. Ordered 2
`; const result = await convertHtmlToBlockNote(html); expect(result.length).toBeGreaterThanOrEqual(4); const bulletItem = result.find(b => b.type === 'bulletListItem'); const numberedItem = result.find(b => b.type === 'numberedListItem'); expect(bulletItem).toBeDefined(); expect(numberedItem).toBeDefined(); }); it('should handle inline styles (bold, italic)', async () => { const html = '

Bold and Italic

'; const result = await convertHtmlToBlockNote(html); const paragraph = result[0]; const content = paragraph.content || []; const boldSegment = content.find((c: any) => c.text === 'Bold'); expect(boldSegment?.styles).toMatchObject({ bold: true }); const italicSegment = content.find((c: any) => c.text === 'Italic'); expect(italicSegment?.styles).toMatchObject({ italic: true }); }); it('should handle links', async () => { const html = '

Click here

'; const result = await convertHtmlToBlockNote(html); const content = result[0].content || []; const linkSegment = content.find((c: any) => c.type === 'link'); expect(linkSegment).toBeDefined(); expect(linkSegment?.href).toBe('https://example.com'); }); it('should convert standalone images to image blocks', async () => { const html = '

My Image

'; const result = await convertHtmlToBlockNote(html); const imageBlock = result.find(b => b.type === 'image'); expect(imageBlock).toBeDefined(); expect(imageBlock?.props?.url).toBe('https://example.com/image.png'); }); it('should strip Outlook style tags and comments', async () => { const html = `

Actual content

`; const result = await convertHtmlToBlockNote(html); // Should contain the paragraph with actual content const paragraph = result.find(b => b.type === 'paragraph'); expect(paragraph).toBeDefined(); const text = paragraph?.content?.find((c: any) => c.type === 'text'); expect(text?.text).toContain('Actual content'); // Should NOT contain CSS text const allText = result .flatMap(b => (b.content || [])) .filter((c: any) => c.type === 'text') .map((c: any) => c.text) .join(' '); expect(allText).not.toContain('font-family'); expect(allText).not.toContain('MsoNormal'); expect(allText).not.toContain('@font-face'); }); it('should strip Outlook conditional comments with CSS inside style tags', async () => { // Outlook/Word emails wrap CSS in and // also use inside

Hello from Outlook

`; const result = await convertHtmlToBlockNote(html); const allText = result .flatMap(b => (b.content || [])) .filter((c: any) => c.type === 'text') .map((c: any) => c.text) .join(' '); expect(allText).toContain('Hello from Outlook'); expect(allText).not.toContain('font-family'); expect(allText).not.toContain('@font-face'); expect(allText).not.toContain('Cambria Math'); expect(allText).not.toContain('MsoNormal'); expect(allText).not.toContain('OfficeDocumentSettings'); }); it('should strip Outlook conditional PIs without comment markers', async () => { // Some Outlook emails use ... without -- prefix const html = `

Content after PI

`; const result = await convertHtmlToBlockNote(html); const allText = result .flatMap(b => (b.content || [])) .filter((c: any) => c.type === 'text') .map((c: any) => c.text) .join(' '); expect(allText).toContain('Content after PI'); expect(allText).not.toContain('MsoTableGrid'); expect(allText).not.toContain('border-collapse'); }); it('should parse HTML tables into table blocks', async () => { const html = `
Column 1Column 2Column3
Row 1sometextto
Row 2fillincells
`; const result = await convertHtmlToBlockNote(html); const tableBlock = result.find(b => b.type === 'table'); expect(tableBlock).toBeDefined(); // Table should have structured content, not flat text const content = tableBlock?.content as any; expect(content).toBeDefined(); expect(content.rows).toBeDefined(); expect(content.rows.length).toBeGreaterThanOrEqual(2); }); }); describe('convertMarkdownToBlocks', () => { it('should handle images split across lines (wrapped markdown)', () => { const markdown = '![Ad]\n(https://example.com/long-url)'; const result = convertMarkdownToBlocks(markdown); const imageBlock = result.find(b => b.type === 'image'); expect(imageBlock).toBeDefined(); expect(imageBlock?.props?.url).toBe('https://example.com/long-url'); expect(imageBlock?.props?.name).toBe('Ad'); }); });