Files
MetonaEditor/tests/index.test.ts
T
thzxx 1cd5b63174
CI / test (16.x) (push) Canceled after 0s
CI / test (18.x) (push) Canceled after 0s
CI / test (20.x) (push) Canceled after 0s
feat: v0.2.1 — reference links, context menu, RTL, ja/ko, regex search, hooks, copy API
## Added
- Reference link/image resolution: [text][ref] + ![alt][ref] with [ref]: url definitions
- Right-click context menu: undo/redo/cut/copy/paste/selectAll + custom items
- RTL CSS layout support for Arabic, Hebrew, Persian etc.
- Japanese (ja) and Korean (ko) locales with 60+ keys each
- Divider position localStorage persistence
- Regex search toggle in search/replace panel
- Export HTML with embedded CSS styles
- beforeChange / afterChange lifecycle hooks (instance + global)
- copyAsMarkdown() / copyAsHTML() clipboard APIs
- CHANGELOG.md, CONTRIBUTING.md, CI workflow (.github/workflows/ci.yml)
- 2 new test suites: index.test.ts, styles.test.ts (684 total tests, +74)

## Changed
- autoSave plugin: closure-based state per instance instead of this context
- Plugin install() now receives options as second argument
- RTL locale detection: now uses language prefix (ar-SA → RTL)
- Rollup dev mode: only builds UMD format
- prepublishOnly now includes typecheck + test
- Version bumped to 0.2.1

## Fixed
- [text][ref] now correctly renders as link (was raw text)
- ![alt][ref] no longer produces empty src
- autoSave plugin state isolation across multiple editor instances
- Footnote definitions no longer consumed by refDef handler
2026-07-25 08:53:58 +08:00

182 lines
5.8 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.
/**
* index.ts 单元测试
* 覆盖全局 APIcreate / use / on / off / setTheme / setLocale / destroy / getStatus
*/
import MeEditor, {
create, use, on, off, setTheme, setLocale, destroy, getStatus,
parseMarkdown, parseTokens, renderTokens, safeUrl, slugify, clearRenderCache,
registerBlockHandler, topologicalSort, validateConfig,
VERSION,
} from '../src/index';
import { MarkdownEditor } from '../src/core';
describe('index.ts - 全局 API', () => {
let container: HTMLElement;
beforeEach(() => {
document.body.innerHTML = '';
container = document.createElement('div');
container.id = 'test-host';
document.body.appendChild(container);
// Set MeEditor on window as index.ts does
(window as any).MeEditor = MeEditor;
});
afterEach(() => {
document.body.innerHTML = '';
});
test('VERSION 是字符串', () => {
expect(typeof VERSION).toBe('string');
expect(VERSION).toBe('0.2.1');
});
test('api.default 是 api 本身', () => {
expect(MeEditor).toBeDefined();
expect(MeEditor.VERSION).toBe(VERSION);
});
test('create 创建编辑器实例', () => {
const editor = create('#test-host', { value: '# Hello' });
expect(editor).toBeInstanceOf(MarkdownEditor);
expect(editor.getValue()).toBe('# Hello');
editor.destroy();
});
test('create 接受 HTMLElement', () => {
const editor = create(container, { value: 'text' });
expect(editor.getValue()).toBe('text');
editor.destroy();
});
test('use 注册全局插件(通过字符串)', () => {
const result = use('autoSave', { delay: 100 });
expect(result).toBe(MeEditor); // 返回 api(链式)
const status = getStatus();
expect(status.globalPlugins).toContain('autoSave');
});
test('use 未知预设插件 warn', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
use('non-existent-preset');
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
test('use 无效插件对象 warn', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
use(null);
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
test('全局插件被注入到新实例', () => {
// Reset global plugins by destroying
destroy();
use('autoSave');
const ed = create(container, { value: 'test' });
const plugins = ed.getPlugins();
expect(plugins.some((p: any) => p.name === 'autoSave')).toBe(true);
ed.destroy();
});
test('on / off 全局钩子', () => {
const fn = jest.fn();
const unsub = on('afterCreate', fn);
expect(typeof unsub).toBe('function');
const ed = create(container, {});
expect(fn).toHaveBeenCalled();
off('afterCreate', fn);
ed.destroy();
});
test('setTheme 切换全局主题', () => {
expect(() => setTheme('dark')).not.toThrow();
});
test('setLocale 切换全局语言', () => {
expect(() => setLocale('en-US')).not.toThrow();
});
test('getStatus 返回状态对象', () => {
const status = getStatus();
expect(status).toHaveProperty('version');
expect(status).toHaveProperty('theme');
expect(status).toHaveProperty('locale');
expect(status).toHaveProperty('globalPlugins');
expect(status).toHaveProperty('presetPlugins');
expect(Array.isArray(status.globalPlugins)).toBe(true);
expect(Array.isArray(status.presetPlugins)).toBe(true);
});
test('destroy 清理全局资源', () => {
expect(() => destroy()).not.toThrow();
const status = getStatus();
expect(status.globalPlugins.length).toBe(0);
});
test('window.MeEditor 被设置', () => {
expect((window as any).MeEditor).toBeDefined();
expect((window as any).MeEditor.VERSION).toBe(VERSION);
});
});
describe('index.ts - 解析器导出', () => {
test('parseMarkdown 可直接调用', () => {
const html = parseMarkdown('# Hello');
expect(html).toContain('<h1');
});
test('parseTokens 可直接调用', () => {
const { tokens, footnotes, refs } = parseTokens('# Hi\n\n**bold**');
expect(Array.isArray(tokens)).toBe(true);
expect(tokens.length).toBeGreaterThan(0);
expect(typeof footnotes).toBe('object');
expect(typeof refs).toBe('object');
});
test('renderTokens 可直接调用', () => {
const { tokens } = parseTokens('**hello**');
const html = renderTokens(tokens);
expect(html).toContain('<strong>');
});
test('safeUrl 过滤危险协议', () => {
expect(safeUrl('javascript:alert(1)')).toBe('');
expect(safeUrl('https://example.com')).toBe('https://example.com');
});
test('slugify 生成锚点 id', () => {
expect(slugify('Hello World')).toBe('hello-world');
});
test('clearRenderCache 不抛错', () => {
expect(() => clearRenderCache()).not.toThrow();
});
test('registerBlockHandler 注册自定义块', () => {
registerBlockHandler({
name: 'testBlock',
priority: 50,
test: () => null,
parse: (_l, i) => ({ token: null, newIndex: i }),
});
// 不抛错即通过
expect(true).toBe(true);
});
});
describe('index.ts - 具名导出', () => {
test('create 是函数', () => expect(typeof create).toBe('function'));
test('use 是函数', () => expect(typeof use).toBe('function'));
test('on 是函数', () => expect(typeof on).toBe('function'));
test('off 是函数', () => expect(typeof off).toBe('function'));
test('setTheme 是函数', () => expect(typeof setTheme).toBe('function'));
test('setLocale 是函数', () => expect(typeof setLocale).toBe('function'));
test('destroy 是函数', () => expect(typeof destroy).toBe('function'));
test('getStatus 是函数', () => expect(typeof getStatus).toBe('function'));
test('topologicalSort 是函数', () => expect(typeof topologicalSort).toBe('function'));
test('validateConfig 是函数', () => expect(typeof validateConfig).toBe('function'));
});