Files
MetonaEditor/tests/highlight.test.ts
T
thzxx cb0caff4cf
CI / test-parser (push) Successful in 9m27s
CI / test-core (push) Successful in 9m34s
CI / test-rest (push) Successful in 9m27s
CI / e2e (push) Failing after 5m15s
CI / verify (20.x) (push) Successful in 9m54s
CI / verify (18.x) (push) Successful in 9m56s
CI / verify (24.x) (push) Successful in 9m48s
feat: v0.4.0 — 安全修复×6 + 增量性能×3 + Playwright E2E + 实例级 i18n
0.3.0 修复版:
- 脚注 id 属性注入 XSS 防护(行内引用 + 脚注区)
- 全局插件注入移至 afterCreate(searchReplace 等依赖 textarea 的插件真正生效)+ use() 同名防重
- 行内渲染缓存附加 refs 指纹,防跨文档引用链接串数据
- 白名单裸标签 <u>/</u> 透传,underline(Ctrl+U)预览可见
- getStatus()/渲染 env 使用实例 locale;replaceAll 替换文本按字面处理
- replaceAllRegex 保留 $1 捕获组语义

0.3.1 性能版:
- 高亮规则按语言缓存(registerLanguage 覆盖失效),bench +33%
- 统计增量计算:字数/词数/行数差异区间,击键零全量扫描
- outline 树构建 O(n²) → 迭代栈 O(n)
- 拖放图片 >500KB 拦截、scrollToLine 真实行高、undo/redo 光标恢复
- selectionChange/cursorMove 与浮动工具栏解耦、unregisterShortcut 大小写归一
- toast 接入 7 种 ANIMATIONS、实例主题订阅随 destroy 断开(dispose)

0.4.0 工程版:
- Playwright 真实浏览器冒烟测试(e2e/,22 项断言)+ CI e2e job
- sideEffects: false 便于 tree-shaking
- MeEditor.destroy() 全面复位(全局钩子清理 + 内部注入钩子重建)
- 实例 locale 统一作用于全部 UI 文案(状态栏/工具栏/右键菜单/大纲)

测试 782 → 826,全绿;typecheck/lint/build 通过
2026-08-09 12:31:14 +08:00

125 lines
4.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('覆盖已有语言定义后规则缓存失效', () => {
registerLanguage('langA', { keywords: ['one'], builtins: [] });
expect(highlight('one', 'langA')).toContain('me-hl-keyword');
registerLanguage('langA', { keywords: ['two'], builtins: [] });
expect(highlight('two', 'langA')).toContain('me-hl-keyword');
expect(highlight('one', 'langA')).not.toContain('me-hl-keyword');
});
test('getSupportedLanguages 返回语言列表', () => {
const langs = getSupportedLanguages();
expect(Array.isArray(langs)).toBe(true);
expect(langs).toContain('javascript');
expect(langs).toContain('typescript');
expect(langs).toContain('python');
});
});
describe('highlight - 环境一致性', () => {
test('字符串高亮与引号转义环境无关(jsdom 断言)', () => {
const out = highlight('const s = "hi"', 'js');
expect(out).toContain('<span class="me-hl-string">"hi"</span>');
});
test('引号不被实体化,span 文本保持原样', () => {
const out = highlight('"a" + \'b\'', 'js');
expect(out).not.toContain('&quot;');
// 双引号字符串应被完整标记
expect(out).toContain('<span class="me-hl-string">"a"</span>');
expect(out).toContain("<span class=\"me-hl-string\">'b'</span>");
});
});