feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库
- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid - 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT - Query Builder链式API + TypeScript泛型支持 - 聚合函数:COUNT/SUM/AVG/MIN/MAX - 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出 - React/Vue框架集成 - 264个测试用例,93.46%覆盖率 - 零运行时依赖
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* PluginManager 完整测试
|
||||
*/
|
||||
|
||||
import { PluginManager } from '../../src/plugin/index';
|
||||
import type { MetonaPlugin } from '../../src/constants';
|
||||
|
||||
describe('PluginManager', () => {
|
||||
let pm: PluginManager;
|
||||
|
||||
beforeEach(() => {
|
||||
pm = new PluginManager();
|
||||
});
|
||||
|
||||
// ---- 注册/卸载 ----
|
||||
|
||||
describe('注册和卸载', () => {
|
||||
it('注册插件', () => {
|
||||
const plugin: MetonaPlugin = {
|
||||
name: 'test-plugin',
|
||||
version: '1.0.0',
|
||||
install: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
pm.register(plugin);
|
||||
expect(pm.getPlugins()).toHaveLength(1);
|
||||
expect(plugin.install).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('按优先级排序', () => {
|
||||
const p1: MetonaPlugin = { name: 'low', version: '1.0', priority: 1, install: jest.fn(), destroy: jest.fn() };
|
||||
const p2: MetonaPlugin = { name: 'high', version: '1.0', priority: 10, install: jest.fn(), destroy: jest.fn() };
|
||||
const p3: MetonaPlugin = { name: 'mid', version: '1.0', priority: 5, install: jest.fn(), destroy: jest.fn() };
|
||||
|
||||
pm.register(p1);
|
||||
pm.register(p2);
|
||||
pm.register(p3);
|
||||
|
||||
const plugins = pm.getPlugins();
|
||||
expect(plugins[0].name).toBe('high');
|
||||
expect(plugins[1].name).toBe('mid');
|
||||
expect(plugins[2].name).toBe('low');
|
||||
});
|
||||
|
||||
it('卸载插件调用 destroy', () => {
|
||||
const destroyFn = jest.fn();
|
||||
const plugin: MetonaPlugin = {
|
||||
name: 'test',
|
||||
version: '1.0',
|
||||
install: jest.fn(),
|
||||
destroy: destroyFn,
|
||||
};
|
||||
pm.register(plugin);
|
||||
pm.unregister('test');
|
||||
expect(destroyFn).toHaveBeenCalled();
|
||||
expect(pm.getPlugins()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('卸载不存在的插件不报错', () => {
|
||||
expect(() => pm.unregister('nonexistent')).not.toThrow();
|
||||
});
|
||||
|
||||
it('getPlugins 返回副本', () => {
|
||||
const plugin: MetonaPlugin = {
|
||||
name: 'test', version: '1.0', install: jest.fn(), destroy: jest.fn(),
|
||||
};
|
||||
pm.register(plugin);
|
||||
const plugins = pm.getPlugins();
|
||||
plugins.push({ name: 'external', version: '1.0', install: jest.fn(), destroy: jest.fn() });
|
||||
expect(pm.getPlugins()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- 钩子 ----
|
||||
|
||||
describe('钩子系统', () => {
|
||||
it('注册和触发钩子', async () => {
|
||||
const callback = jest.fn();
|
||||
pm.on('beforeInsert', callback);
|
||||
await pm.trigger('beforeInsert', { id: '1' });
|
||||
expect(callback).toHaveBeenCalledWith({ id: '1' });
|
||||
});
|
||||
|
||||
it('多个回调', async () => {
|
||||
const cb1 = jest.fn();
|
||||
const cb2 = jest.fn();
|
||||
pm.on('afterQuery', cb1);
|
||||
pm.on('afterQuery', cb2);
|
||||
await pm.trigger('afterQuery', 'SELECT *', []);
|
||||
expect(cb1).toHaveBeenCalled();
|
||||
expect(cb2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('卸载钩子', async () => {
|
||||
const cb = jest.fn();
|
||||
pm.on('beforeUpdate', cb);
|
||||
pm.off('beforeUpdate', cb);
|
||||
await pm.trigger('beforeUpdate');
|
||||
expect(cb).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('触发未注册的钩子不报错', async () => {
|
||||
await expect(pm.trigger('beforeInsert')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('卸载不存在的回调不报错', () => {
|
||||
expect(() => pm.off('beforeInsert', jest.fn())).not.toThrow();
|
||||
});
|
||||
|
||||
it('异步回调', async () => {
|
||||
const cb = jest.fn().mockResolvedValue(undefined);
|
||||
pm.on('beforeQuery', cb);
|
||||
await pm.trigger('beforeQuery');
|
||||
expect(cb).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ---- 生命周期钩子覆盖 ----
|
||||
|
||||
describe('全部钩子类型', () => {
|
||||
const hooks = [
|
||||
'beforeCreateTable', 'afterCreateTable',
|
||||
'beforeDropTable', 'afterDropTable',
|
||||
'beforeInsert', 'afterInsert',
|
||||
'beforeUpdate', 'afterUpdate',
|
||||
'beforeDelete', 'afterDelete',
|
||||
'beforeQuery', 'afterQuery',
|
||||
'beforeTransaction', 'afterTransaction',
|
||||
] as const;
|
||||
|
||||
hooks.forEach((hook) => {
|
||||
it(`钩子 ${hook} 可注册和触发`, async () => {
|
||||
const cb = jest.fn();
|
||||
pm.on(hook, cb);
|
||||
await pm.trigger(hook);
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---- destroy ----
|
||||
|
||||
describe('销毁', () => {
|
||||
it('destroy 调用所有插件的 destroy', () => {
|
||||
const d1 = jest.fn();
|
||||
const d2 = jest.fn();
|
||||
pm.register({ name: 'p1', version: '1.0', install: jest.fn(), destroy: d1 });
|
||||
pm.register({ name: 'p2', version: '1.0', install: jest.fn(), destroy: d2 });
|
||||
pm.destroy();
|
||||
expect(d1).toHaveBeenCalled();
|
||||
expect(d2).toHaveBeenCalled();
|
||||
expect(pm.getPlugins()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('destroy 清空钩子', async () => {
|
||||
const cb = jest.fn();
|
||||
pm.on('beforeInsert', cb);
|
||||
pm.destroy();
|
||||
await pm.trigger('beforeInsert');
|
||||
expect(cb).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('插件 destroy 出错不影响销毁流程', () => {
|
||||
const badPlugin: MetonaPlugin = {
|
||||
name: 'bad',
|
||||
version: '1.0',
|
||||
install: jest.fn(),
|
||||
destroy: () => { throw new Error('destroy failed'); },
|
||||
};
|
||||
const goodPlugin: MetonaPlugin = {
|
||||
name: 'good',
|
||||
version: '1.0',
|
||||
install: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
pm.register(badPlugin);
|
||||
pm.register(goodPlugin);
|
||||
expect(() => pm.destroy()).not.toThrow();
|
||||
expect(goodPlugin.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user