feat: v0.4.0 — 安全修复×6 + 增量性能×3 + Playwright E2E + 实例级 i18n
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
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
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 通过
This commit is contained in:
@@ -1329,6 +1329,93 @@ describe('parseMarkdown - v0.2.5 title 转义', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMarkdown - v0.2.5 脚注 id 属性注入防护', () => {
|
||||
test('脚注引用 id 中的引号不能注入属性', () => {
|
||||
const html = parseMarkdown('[^a" onclick="alert(1)]\n\n[^a" onclick="alert(1)]: note');
|
||||
expect(html).not.toContain(' onclick="');
|
||||
expect(html).not.toContain('onclick="alert(1)"');
|
||||
expect(html).toContain('" onclick');
|
||||
});
|
||||
|
||||
test('脚注区 id 中的引号被转义', () => {
|
||||
const html = parseMarkdown('x[^a" onload="x]\n\n[^a" onload="x]: note');
|
||||
expect(html).not.toContain(' onload="');
|
||||
expect(html).toContain('" onload');
|
||||
});
|
||||
|
||||
test('正常脚注 id 不受影响', () => {
|
||||
const html = parseMarkdown('text[^1]\n\n[^1]: note');
|
||||
expect(html).toContain('id="fnref-1"');
|
||||
expect(html).toContain('id="fn-1"');
|
||||
});
|
||||
});
|
||||
|
||||
// ============ v0.3.0 白名单裸标签透传(underline) ============
|
||||
|
||||
describe('parseMarkdown - v0.3.0 underline 渲染', () => {
|
||||
test('裸 <u> 标签透传为下划线', () => {
|
||||
const html = parseMarkdown('<u>underlined</u>');
|
||||
expect(html).toContain('<p><u>underlined</u></p>');
|
||||
});
|
||||
|
||||
test('Ctrl+U 命令输出在预览中可见', () => {
|
||||
const html = parseMarkdown('<u>hello</u> world');
|
||||
expect(html).toContain('<u>hello</u>');
|
||||
expect(html).not.toContain('<u>');
|
||||
});
|
||||
|
||||
test('带属性的 <u onclick> 仍被转义', () => {
|
||||
const html = parseMarkdown('<u onclick="alert(1)">x</u>');
|
||||
expect(html).not.toContain('<u onclick');
|
||||
expect(html).toContain('<u');
|
||||
});
|
||||
|
||||
test('行内代码中的 <u> 不被透传', () => {
|
||||
const html = parseMarkdown('`<u>x</u>`');
|
||||
expect(html).toContain('<code><u>x</u></code>');
|
||||
expect(html).not.toContain('<u>');
|
||||
});
|
||||
|
||||
test('代码块中的 <u> 不被透传', () => {
|
||||
const html = parseMarkdown('```\n<u>x</u>\n```');
|
||||
expect(html).toContain('<u>');
|
||||
expect(html).not.toContain('<u>');
|
||||
});
|
||||
|
||||
test('<script> 仍被转义(白名单外)', () => {
|
||||
const html = parseMarkdown('<script>alert(1)</script>');
|
||||
expect(html).not.toContain('<script>');
|
||||
});
|
||||
});
|
||||
|
||||
// ============ v0.3.0 渲染缓存 refs 指纹 ============
|
||||
|
||||
describe('parseMarkdown - v0.3.0 渲染缓存 refs 指纹', () => {
|
||||
test('相同文本不同引用定义互不串数据', () => {
|
||||
clearRenderCache();
|
||||
const htmlA = parseMarkdown('[x][r]\n\n[r]: https://a.com');
|
||||
const htmlB = parseMarkdown('[x][r]\n\n[r]: https://b.com');
|
||||
expect(htmlA).toContain('href="https://a.com"');
|
||||
expect(htmlB).toContain('href="https://b.com"');
|
||||
expect(htmlA).not.toContain('href="https://b.com"');
|
||||
expect(htmlB).not.toContain('href="https://a.com"');
|
||||
});
|
||||
|
||||
test('无引用定义的文本仍可命中缓存', () => {
|
||||
clearRenderCache();
|
||||
expect(parseMarkdown('**bold** text')).toBe(parseMarkdown('**bold** text'));
|
||||
});
|
||||
|
||||
test('引用链接与无引用文本混合时各自正确', () => {
|
||||
clearRenderCache();
|
||||
const htmlA = parseMarkdown('[x][r] and **plain**\n\n[r]: https://a.com');
|
||||
const htmlB = parseMarkdown('[x][r] and **plain**\n\n[r]: https://b.com');
|
||||
expect(htmlA).toContain('https://a.com');
|
||||
expect(htmlB).toContain('https://b.com');
|
||||
expect(htmlB).toContain('<strong>plain</strong>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMarkdown - v0.2.5 URL 属性无双重转义', () => {
|
||||
test('链接 URL 中的 & 不双重转义', () => {
|
||||
const html = parseMarkdown('[x](https://example.com/?a=1&b=2)');
|
||||
|
||||
Reference in New Issue
Block a user