feat: 增加 plugin 的 autoInit 注册方式

chore(test): 增加 plugin 的单测
This commit is contained in:
力皓 2021-01-22 16:30:55 +08:00
parent 20f3a927ae
commit 4f9be73b61
10 changed files with 218 additions and 30 deletions

View File

@ -20,6 +20,7 @@ module.exports = {
'!src/icons/**',
'!src/locale/**',
'!src/builtin-simulator/utils/**',
'!src/plugin/sequencify.ts',
'!src/document/node/exclusive-group.ts',
'!**/node_modules/**',
'!**/vendor/**',

View File

@ -48,7 +48,7 @@ export class LiveEditing {
const npm = node?.componentMeta?.npm;
const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || '';
editor?.emit('designer.builinSimulator.LiveEditing', {
editor?.emit('designer.builtinSimulator.liveEditing', {
selected,
});

View File

@ -1,5 +1,5 @@
import { Editor } from '@ali/lowcode-editor-core';
import { CompositeObject, ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager, ILowCodePluginContext } from './plugin-types';
import { ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager, ILowCodePluginContext, LowCodeRegisterOptions } from './plugin-types';
import { LowCodePlugin } from './plugin';
import LowCodePluginContext from './plugin-context';
import { getLogger, invariant } from '../utils';
@ -22,19 +22,23 @@ export class LowCodePluginManager implements ILowCodePluginManager {
return new LowCodePluginContext(this.editor, this);
}
register(
pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig,
options?: CompositeObject,
): void {
async register(
pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig,
pluginOptions?: any,
options?: LowCodeRegisterOptions,
): Promise<void> {
const ctx = this._getLowCodePluginContext();
const config = pluginConfig(ctx, options);
const config = pluginConfigCreator(ctx, pluginOptions);
invariant(config.name, `${config.name} required`, config);
ctx.setLogger(config);
invariant(!this.pluginsMap.has(config.name), `${config.name} already exists`, this.pluginsMap.get(config.name));
const plugin = new LowCodePlugin(this, config, options);
const plugin = new LowCodePlugin(this, config, pluginOptions);
if (options?.autoInit) {
await plugin.init();
}
this.plugins.push(plugin);
this.pluginsMap.set(plugin.name, plugin);
logger.log('plugin registered with config:', config, ', options:', options);
logger.log('plugin registered with config:', config, ', options:', pluginOptions);
}
get(pluginName: string): ILowCodePlugin | undefined {
@ -51,7 +55,7 @@ export class LowCodePluginManager implements ILowCodePluginManager {
async delete(pluginName: string): Promise<boolean> {
const idx = this.plugins.findIndex(plugin => plugin.name === pluginName);
if (idx < -1) return false;
if (idx === -1) return false;
const plugin = this.plugins[idx];
await plugin.destroy();

View File

@ -10,9 +10,9 @@ import {
export interface ILowCodePluginConfig {
name: string;
dep?: string[]; // 依赖插件名
init(): void;
init?(): void;
destroy?(): void;
exports?(): CompositeObject;
exports?(): any;
}
export interface ILowCodePluginCore {
@ -22,10 +22,10 @@ export interface ILowCodePluginCore {
config: ILowCodePluginConfig;
logger: Logger;
on(event: string | symbol, listener: (...args: any[]) => void): any;
off(event: string | symbol, listener: (...args: any[]) => void): any;
emit(event: string | symbol, ...args: any[]): boolean;
removeAllListeners(event?: string | symbol): this;
init(): void;
init(forceInit?: boolean): void;
isInited(): boolean;
destroy(): void;
toProxy(): any;
setDisabled(flag: boolean): void;
@ -62,9 +62,10 @@ interface ILowCodePluginManagerPluginAccessor {
export interface ILowCodePluginManagerCore {
register(
pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig,
pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig,
pluginOptions?: any,
options?: CompositeObject,
): void;
): Promise<void>;
get(pluginName: string): ILowCodePlugin | undefined;
getAll(): ILowCodePlugin[];
has(pluginName: string): boolean;
@ -74,3 +75,7 @@ export interface ILowCodePluginManagerCore {
}
export type ILowCodePluginManager = ILowCodePluginManagerCore & ILowCodePluginManagerPluginAccessor;
export type LowCodeRegisterOptions = {
autoInit?: boolean;
};

View File

@ -2,8 +2,8 @@ import {
ILowCodePlugin,
ILowCodePluginConfig,
ILowCodePluginManager,
CompositeObject,
} from '@ali/lowcode-types';
} from './plugin-types';
import { CompositeObject } from '@ali/lowcode-types';
import { EventEmitter } from 'events';
import { getLogger, Logger, invariant } from '../utils';
@ -50,30 +50,36 @@ export class LowCodePlugin implements ILowCodePlugin {
}
on(event: string | symbol, listener: (...args: any[]) => void): any {
return this.emitter.on(event, listener);
this.emitter.on(event, listener);
return () => {
this.emitter.off(event, listener);
};
}
emit(event: string | symbol, ...args: any[]) {
return this.emitter.emit(event, ...args);
}
off(event: string | symbol, listener: (...args: any[]) => void): any {
return this.emitter.off(event, listener);
}
removeAllListeners(event: string | symbol): any {
return this.emitter.removeAllListeners(event);
}
async init() {
isInited() {
return this._inited;
}
async init(forceInit?: boolean) {
if (this._inited && !forceInit) return;
this.logger.log('method init called');
await this.config.init?.call(undefined);
this._inited = true;
}
async destroy() {
if (!this._inited) return;
this.logger.log('method destroy called');
await this.config?.destroy?.call(undefined);
this._inited = false;
}
setDisabled(flag = true) {
@ -93,7 +99,7 @@ export class LowCodePlugin implements ILowCodePlugin {
});
}
dispose() {
return this.manager.delete(this.name);
async dispose() {
await this.manager.delete(this.name);
}
}

View File

@ -0,0 +1,172 @@
import '../fixtures/window';
import { Editor } from '@ali/lowcode-editor-core';
import { LowCodePluginManager } from '../../src/plugin/plugin-manager';
import { ILowCodePluginContext, ILowCodePluginManager } from '../../src/plugin/plugin-types';
const editor = new Editor();
describe('plugin 测试', () => {
let pluginManager: ILowCodePluginManager;
beforeEach(() => {
pluginManager = new LowCodePluginManager(editor).toProxy();
});
afterEach(() => {
pluginManager.dispose();
});
it('注册插件,插件参数生成函数能被调用,且能拿到正确的 ctx / options', () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
mockFn(ctx, options);
return {
name: 'demo1',
};
}, { test: 1 });
const [expectedCtx, expectedOptions] = mockFn.mock.calls[0];
expect(expectedCtx).toHaveProperty('designer');
expect(expectedCtx).toHaveProperty('designerCabin');
expect(expectedCtx).toHaveProperty('editor');
expect(expectedCtx).toHaveProperty('hotkey');
expect(expectedCtx).toHaveProperty('plugins');
expect(expectedCtx).toHaveProperty('skeleton');
expect(expectedCtx).toHaveProperty('logger');
expect(expectedOptions).toEqual({ test: 1 });
});
it('注册插件,调用插件 init 方法', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
init: mockFn,
exports() {
return {
x: 1,
y: 2,
}
}
};
}, { test: 1 });
await pluginManager.init();
expect(pluginManager.size).toBe(1);
expect(pluginManager.has('demo1')).toBeTruthy();
expect(pluginManager.get('demo1')!.isInited()).toBeTruthy();
expect(pluginManager.demo1).toBeTruthy();
expect(pluginManager.demo1.x).toBe(1);
expect(pluginManager.demo1.y).toBe(2);
expect(mockFn).toHaveBeenCalled();
});
it('注册插件,调用 setDisabled 方法', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
init: mockFn,
};
}, { test: 1 });
await pluginManager.init();
expect(pluginManager.demo1).toBeTruthy();
pluginManager.setDisabled('demo1', true);
expect(pluginManager.demo1).toBeUndefined();
});
it('删除插件,调用插件 destory 方法', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
destroy: mockFn,
};
}, { test: 1 });
await pluginManager.init();
await pluginManager.delete('demo1');
expect(mockFn).toHaveBeenCalled();
await pluginManager.delete('non-existing');
});
it('dep 依赖', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
dep: ['demo2'],
init: () => mockFn('demo1'),
};
});
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo2',
init: () => mockFn('demo2'),
};
});
await pluginManager.init();
expect(mockFn).toHaveBeenNthCalledWith(1, 'demo2');
expect(mockFn).toHaveBeenNthCalledWith(2, 'demo1');
});
it('autoInit 功能', async () => {
const mockFn = jest.fn();
await pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
init: mockFn,
};
}, { test: 1 }, { autoInit: true });
expect(mockFn).toHaveBeenCalled();
});
it('插件不会重复 init除非强制重新 init', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
init: mockFn,
};
}, { test: 1 });
await pluginManager.init();
expect(mockFn).toHaveBeenCalledTimes(1);
pluginManager.get('demo1')!.init();
expect(mockFn).toHaveBeenCalledTimes(1);
pluginManager.get('demo1')!.init(true);
expect(mockFn).toHaveBeenCalledTimes(2);
});
it('内部事件机制', async () => {
const mockFn = jest.fn();
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
};
}, { test: 1 });
await pluginManager.init();
const plugin = pluginManager.get('demo1')!;
plugin.on('haha', mockFn);
plugin.emit('haha', 1, 2, 3);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
plugin.removeAllListeners('haha');
plugin.emit('haha', 1, 2, 3);
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('dispose 方法', async () => {
pluginManager.register((ctx: ILowCodePluginContext, options: any) => {
return {
name: 'demo1',
};
}, { test: 1 });
await pluginManager.init();
const plugin = pluginManager.get('demo1')!;
await plugin.dispose();
expect(pluginManager.has('demo1')).toBeFalsy();
})
});

View File

@ -20,7 +20,7 @@
"dependencies": {
"@ali/lowcode-designer": "^1.0.31",
"@ali/lowcode-editor-core": "^1.0.31",
"@ali/lowcode-editor-setters": "^1.0.22",
"@ali/lowcode-editor-setters": "1.0.29",
"@ali/lowcode-editor-skeleton": "^1.0.31",
"@ali/lowcode-plugin-designer": "^1.0.31",
"@ali/lowcode-plugin-outline-pane": "^1.0.31",

View File

@ -24,6 +24,7 @@
"@ali/lowcode-plugin-designer": "^1.0.31",
"@ali/lowcode-plugin-outline-pane": "^1.0.31",
"@ali/lowcode-utils": "^1.0.31",
"@ali/lowcode-editor-setters": "1.0.29",
"@ali/ve-i18n-util": "^2.0.0",
"@ali/ve-icons": "^4.1.9",
"@ali/ve-less-variables": "2.0.3",

View File

@ -17,4 +17,3 @@ export * from './node';
export * from './transform-stage';
export * from './code-intermediate';
export * from './code-result';
export * from './plugin';

View File

@ -180,8 +180,8 @@ export interface Callbacks {
onMouseDownHook?: (e: MouseEvent, currentNode: any) => any;
onDblClickHook?: (e: MouseEvent, currentNode: any) => any;
onClickHook?: (e: MouseEvent, currentNode: any) => any;
onLocateHook?: (e: any, currentNode: any) => any;
onAcceptHook?: (currentNode: any, locationData: any) => any;
// onLocateHook?: (e: any, currentNode: any) => any;
// onAcceptHook?: (currentNode: any, locationData: any) => any;
onMoveHook?: (currentNode: any) => boolean; // thinkof 限制性拖拽
onChildMoveHook?: (childNode: any, currentNode: any) => boolean;