Files
MetonaEditor/tests/highlight.test.ts
T
thzxx 5919dd9f66 feat: v0.2.5 — 内置语法高亮 + exportPDF + benchmark + CI 并行
- 新增 src/highlight.ts 零依赖轻量高亮器:13 种语言,先转义后
  着色保证安全,输出 me-hl-* 类,MeEditor.highlight 即插即用
- exportTool 新增 editor.exportPDF()(隐藏 iframe + window.print),
  exportHTML/exportPDF 共用 HTML 构建逻辑
- imagePaste 支持 maxSizeKB 限制,超限 toast 警告
- npm run bench 性能基准:parseMarkdown 1MB ≈ 51ms
- CI 测试拆分为 parser/core/rest 三个并行 job + 跨版本 verify job
- docs.html/README 补齐新 API、事件、高亮文档;取消跟踪 dist 产物;
  版本 0.2.4 → 0.2.5,测试 725 → 768
2026-08-09 08:59:55 +08:00

102 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* highlight.ts 单元测试
* 覆盖内置轻量语法高亮器的安全性与标记输出
*/
import { highlight, normalizeLanguage, registerLanguage, getSupportedLanguages } from '../src/highlight';
describe('highlight - 基础', () => {
test('未知语言返回转义纯文本', () => {
const out = highlight('const x = 1;', 'nonexistent');
expect(out).toBe('const x = 1;');
});
test('支持的语言别名归一化', () => {
expect(normalizeLanguage('js')).toBe('javascript');
expect(normalizeLanguage('ts')).toBe('typescript');
expect(normalizeLanguage('py')).toBe('python');
expect(normalizeLanguage('sh')).toBe('bash');
expect(normalizeLanguage('yml')).toBe('yaml');
expect(normalizeLanguage('Java')).toBe('java');
expect(normalizeLanguage('unknown')).toBe('');
expect(normalizeLanguage('')).toBe('');
});
test('关键词被标记', () => {
const out = highlight('const x = 1', 'js');
expect(out).toContain('<span class="me-hl-keyword">const</span>');
});
test('字符串被标记', () => {
const out = highlight('const s = "hello"', 'js');
expect(out).toContain('<span class="me-hl-string">"hello"</span>');
});
test('注释被标记', () => {
const out = highlight('// comment\nconst x = 1', 'js');
expect(out).toContain('<span class="me-hl-comment">// comment</span>');
});
test('数字被标记', () => {
const out = highlight('const n = 42', 'js');
expect(out).toContain('<span class="me-hl-number">42</span>');
});
test('函数调用被标记', () => {
const out = highlight('foo(bar)', 'js');
expect(out).toContain('<span class="me-hl-function">foo</span>');
});
test('python 井号注释与 def 关键词', () => {
const out = highlight('# note\ndef main():', 'python');
expect(out).toContain('<span class="me-hl-comment"># note</span>');
expect(out).toContain('<span class="me-hl-keyword">def</span>');
});
test('内置全局对象被标记', () => {
const out = highlight('console.log("x")', 'js');
expect(out).toContain('<span class="me-hl-builtin">console</span>');
});
});
describe('highlight - 安全性', () => {
test('HTML 特殊字符被转义,不产生裸标签', () => {
const out = highlight('<script>alert(1)</script>', 'js');
expect(out).not.toContain('<script>');
expect(out).not.toContain('<script');
expect(out).toContain('&lt;script&gt;');
});
test('字符串中的引号安全', () => {
const out = highlight('const s = "a<b>&"', 'js');
expect(out).not.toContain('a<b');
expect(out).toContain('a&lt;b&gt;');
});
test('恶意输入不破坏 span 结构', () => {
const out = highlight('"</span><script>x</script>"', 'js');
// 所有 span 配对(每开一个 <span 就有一个 </span>
const opens = (out.match(/<span/g) || []).length;
const closes = (out.match(/<\/span>/g) || []).length;
expect(opens).toBe(closes);
expect(out).not.toContain('<script');
});
});
describe('highlight - 语言注册', () => {
test('注册自定义语言', () => {
registerLanguage('foo', { keywords: ['frobnicate'], builtins: [] });
expect(normalizeLanguage('foo')).toBe('foo');
const out = highlight('frobnicate the widget', 'foo');
expect(out).toContain('<span class="me-hl-keyword">frobnicate</span>');
});
test('getSupportedLanguages 返回语言列表', () => {
const langs = getSupportedLanguages();
expect(Array.isArray(langs)).toBe(true);
expect(langs).toContain('javascript');
expect(langs).toContain('typescript');
expect(langs).toContain('python');
});
});