import { describe, it, expect } from 'vitest'; import { decodeHTMLEntities, htmlToText, htmlToMarkdown, isBlockedPage, computeRelevance, } from '../src/main/html-utils.js'; describe('decodeHTMLEntities', () => { it('解码常见命名实体', () => { expect(decodeHTMLEntities('<div>&"x"')).toBe('
&"x"'); expect(decodeHTMLEntities(' ')).toBe(' '); }); it('解码十进制数字实体', () => { expect(decodeHTMLEntities('AB')).toBe('AB'); }); it('解码十六进制数字实体', () => { expect(decodeHTMLEntities('AB')).toBe('AB'); }); it('无实体时原样返回', () => { expect(decodeHTMLEntities('plain text')).toBe('plain text'); }); it('多实体混合解码', () => { expect(decodeHTMLEntities('© 2026 — €10')).toBe('\u00A9 2026 \u2014 \u20AC10'); }); }); describe('htmlToText', () => { it('移除 script/style 噪音标签', () => { const html = '

正文内容

'; const text = htmlToText(html); expect(text).toContain('正文内容'); expect(text).not.toContain('alert'); expect(text).not.toContain('display:none'); }); it('块级标签转为换行', () => { const text = htmlToText('
第一段
第二段
'); expect(text).toContain('第一段'); expect(text).toContain('第二段'); }); it('去除剩余标签并解码实体', () => { const text = htmlToText('

hello & goodbye

'); expect(text).toBe('hello & goodbye'); }); it('空输入返回空', () => { expect(htmlToText('')).toBe(''); }); }); describe('htmlToMarkdown', () => { it('标题转为 Markdown 标题', () => { const md = htmlToMarkdown('

大标题

副标题

'); expect(md).toContain('# 大标题'); expect(md).toContain('## 副标题'); }); it('链接转为 Markdown 链接', () => { const md = htmlToMarkdown('example'); expect(md).toContain('[example](https://example.com)'); }); it('代码块转为围栏代码', () => { const md = htmlToMarkdown('
const x = 1;
'); expect(md).toContain('```'); expect(md).toContain('const x = 1;'); }); it('列表项转为 - 列表', () => { const md = htmlToMarkdown(''); expect(md).toContain('- 项目A'); expect(md).toContain('- 项目B'); }); it('加粗/斜体标签转换', () => { const md = htmlToMarkdown('加粗斜体'); expect(md).toContain('**加粗**'); expect(md).toContain('*斜体*'); }); }); describe('isBlockedPage', () => { it('短内容视为拦截页', () => { expect(isBlockedPage('')).toBe(true); }); it('Cloudflare 拦截特征', () => { const html = 'Just a moment...'.repeat(5); expect(isBlockedPage(html)).toBe(true); }); it('403 拦截特征', () => { const html = '403 Forbidden'.repeat(10); expect(isBlockedPage(html)).toBe(true); }); it('验证码特征', () => { const html = '请启用JavaScript'.repeat(10); expect(isBlockedPage(html)).toBe(true); }); it('正常长页面不视为拦截', () => { const html = '' + '

正常内容

'.repeat(50) + ''; expect(isBlockedPage(html)).toBe(false); }); }); describe('computeRelevance', () => { it('无 query 返回中性 50 分', () => { expect(computeRelevance('', '标题', '摘要')).toBe(50); }); it('CJK 关键词命中标题得高分', () => { const score = computeRelevance('rust 语言', 'Rust 语言教程', '本教程介绍 rust'); expect(score).toBeGreaterThanOrEqual(25); }); it('英文词命中标题得 15 分', () => { const score = computeRelevance('rust backend', 'rust backend guide', 'a guide'); expect(score).toBeGreaterThanOrEqual(15); }); it('完全无关标题得 0 分', () => { const score = computeRelevance('rust', 'cooking recipes', 'food'); expect(score).toBe(0); }); it('得分上限 100', () => { const score = computeRelevance('rust language guide', 'rust language guide', 'rust language guide'); expect(score).toBeLessThanOrEqual(100); }); });