import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { htmlToMarkdown, downloadMarkdownFile, SAMPLE_HTML } from '../htmlToMarkdown'; describe('htmlToMarkdown', () => { it('returns empty result for empty string', () => { const result = htmlToMarkdown(''); expect(result.markdown).toBe(''); expect(result.originalLength).toBe(0); expect(result.markdownLength).toBe(0); expect(result.hasError).toBe(false); }); it('returns empty result for whitespace-only string', () => { const result = htmlToMarkdown(' \n '); expect(result.markdown).toBe(''); expect(result.hasError).toBe(false); }); it('converts h1 heading', () => { const result = htmlToMarkdown('

Hello World

'); expect(result.markdown).toContain('# Hello World'); }); it('converts h2-h6 headings', () => { const result = htmlToMarkdown( '

Two

Three

Four

Five
Six
', ); expect(result.markdown).toContain('## Two'); expect(result.markdown).toContain('### Three'); expect(result.markdown).toContain('#### Four'); expect(result.markdown).toContain('##### Five'); expect(result.markdown).toContain('###### Six'); }); it('converts paragraph', () => { const result = htmlToMarkdown('

This is a paragraph.

'); expect(result.markdown).toContain('This is a paragraph.'); }); it('converts strong and b tags', () => { const result = htmlToMarkdown('bold and also bold'); expect(result.markdown).toContain('**bold**'); expect(result.markdown).toContain('**also bold**'); }); it('converts em and i tags', () => { const result = htmlToMarkdown('italic and also italic'); expect(result.markdown).toContain('*italic*'); expect(result.markdown).toContain('*also italic*'); }); it('converts del and s tags', () => { const result = htmlToMarkdown('deleted and strikethrough'); expect(result.markdown).toContain('~~deleted~~'); expect(result.markdown).toContain('~~strikethrough~~'); }); it('converts inline code', () => { const result = htmlToMarkdown('const x = 1;'); expect(result.markdown).toContain('`const x = 1;`'); }); it('converts pre code block', () => { const result = htmlToMarkdown('
line1\nline2
'); expect(result.markdown).toContain('```'); expect(result.markdown).toContain('line1'); expect(result.markdown).toContain('line2'); }); it('converts pre code block with language', () => { const result = htmlToMarkdown( '
const x = 1;
', ); expect(result.markdown).toContain('```javascript'); expect(result.markdown).toContain('const x = 1;'); }); it('converts anchor links', () => { const result = htmlToMarkdown('Link text'); expect(result.markdown).toContain('[Link text](https://example.com)'); }); it('converts anchor links with title', () => { const result = htmlToMarkdown('Link'); expect(result.markdown).toContain('[Link](https://example.com "Title")'); }); it('converts images', () => { const result = htmlToMarkdown('desc'); expect(result.markdown).toContain('![desc](image.png)'); }); it('converts images with title', () => { const result = htmlToMarkdown('desc'); expect(result.markdown).toContain('![desc](image.png "Title")'); }); it('converts unordered list', () => { const result = htmlToMarkdown(''); expect(result.markdown).toContain('- Item 1'); expect(result.markdown).toContain('- Item 2'); }); it('converts ordered list', () => { const result = htmlToMarkdown('
  1. First
  2. Second
'); expect(result.markdown).toContain('1. First'); expect(result.markdown).toContain('2. Second'); }); it('converts ordered list with start attribute', () => { const result = htmlToMarkdown('
  1. Item
'); expect(result.markdown).toContain('5. Item'); }); it('converts blockquote', () => { const result = htmlToMarkdown('

Quote text

'); expect(result.markdown).toContain('> Quote text'); }); it('converts horizontal rule', () => { const result = htmlToMarkdown('
'); expect(result.markdown).toContain('---'); }); it('converts line break', () => { const result = htmlToMarkdown('Line 1
Line 2'); expect(result.markdown).toContain('\n'); }); it('converts table', () => { const html = '
NameType
JohnUser
'; const result = htmlToMarkdown(html); expect(result.markdown).toContain('| Name | Type |'); expect(result.markdown).toContain('| --- | --- |'); expect(result.markdown).toContain('| John | User |'); }); it('ignores script and style tags', () => { const result = htmlToMarkdown('

text

'); expect(result.markdown).not.toContain('alert'); expect(result.markdown).not.toContain('.x{}'); expect(result.markdown).toContain('text'); }); it('handles nested elements', () => { const result = htmlToMarkdown('

bold and italic

'); expect(result.markdown).toContain('**bold**'); expect(result.markdown).toContain('*italic*'); }); it('returns hasError false for valid HTML', () => { const result = htmlToMarkdown('

Valid

'); expect(result.hasError).toBe(false); expect(result.error).toBeUndefined(); }); it('returns correct length stats', () => { const html = '

Hello

'; const result = htmlToMarkdown(html); expect(result.originalLength).toBe(html.length); expect(result.markdownLength).toBe(result.markdown.length); }); it('handles full HTML document', () => { const result = htmlToMarkdown(SAMPLE_HTML); expect(result.hasError).toBe(false); expect(result.markdown).toContain('# 欢迎使用 HTML 转 Markdown'); expect(result.markdown).toContain('**HTML**'); expect(result.markdown).toContain('*Markdown*'); expect(result.markdown).toContain('```javascript'); expect(result.markdown).toContain('| 名称 | 类型 |'); expect(result.markdown).toContain('> 这是一段引用文本'); }); it('handles plain text without tags', () => { const result = htmlToMarkdown('Just plain text'); expect(result.markdown).toContain('Just plain text'); expect(result.hasError).toBe(false); }); it('handles task list items', () => { const result = htmlToMarkdown( '', ); expect(result.markdown).toContain('- [x] Done'); expect(result.markdown).toContain('- [ ] Todo'); }); it('handles div and span wrappers', () => { const result = htmlToMarkdown('

Content

'); expect(result.markdown).toContain('Content'); }); it('handles empty anchor with no text', () => { const result = htmlToMarkdown(''); expect(result.markdown).not.toContain('['); }); it('handles unknown tags gracefully', () => { const result = htmlToMarkdown('Content'); expect(result.markdown).toContain('Content'); }); }); describe('downloadMarkdownFile', () => { const originalURL = globalThis.URL; let createObjectURLSpy: ReturnType; let revokeObjectURLSpy: ReturnType; let clickSpy: ReturnType; let appendChildSpy: ReturnType; let removeChildSpy: ReturnType; beforeEach(() => { createObjectURLSpy = vi.fn().mockReturnValue('blob:test-url'); revokeObjectURLSpy = vi.fn(); (globalThis as any).URL = { createObjectURL: createObjectURLSpy, revokeObjectURL: revokeObjectURLSpy, }; clickSpy = vi.fn(); appendChildSpy = vi.fn(); removeChildSpy = vi.fn(); const mockLink = { href: '', download: '', click: clickSpy, }; vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any); vi.spyOn(document.body, 'appendChild').mockImplementation(appendChildSpy as any); vi.spyOn(document.body, 'removeChild').mockImplementation(removeChildSpy as any); }); afterEach(() => { vi.restoreAllMocks(); (globalThis as any).URL = originalURL; }); it('should create a blob and trigger download', () => { downloadMarkdownFile('# Hello', 'test.md'); expect(createObjectURLSpy).toHaveBeenCalledOnce(); expect(clickSpy).toHaveBeenCalledOnce(); expect(appendChildSpy).toHaveBeenCalledOnce(); expect(removeChildSpy).toHaveBeenCalledOnce(); expect(revokeObjectURLSpy).toHaveBeenCalledOnce(); }); it('should use default filename when not provided', () => { downloadMarkdownFile('content'); const mockLink = (document.createElement as any).mock.results[0].value; expect(mockLink.download).toBe('export.md'); }); it('should set correct MIME type', () => { downloadMarkdownFile('content'); const blobArg = createObjectURLSpy.mock.calls[0][0] as Blob; expect(blobArg.type).toBe('text/markdown;charset=utf-8'); }); });