mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-19 22:58:15 +00:00
feat: 增加 plugin 的 autoInit 注册方式
chore(test): 增加 plugin 的单测
This commit is contained in:
parent
20f3a927ae
commit
4f9be73b61
@ -20,6 +20,7 @@ module.exports = {
|
|||||||
'!src/icons/**',
|
'!src/icons/**',
|
||||||
'!src/locale/**',
|
'!src/locale/**',
|
||||||
'!src/builtin-simulator/utils/**',
|
'!src/builtin-simulator/utils/**',
|
||||||
|
'!src/plugin/sequencify.ts',
|
||||||
'!src/document/node/exclusive-group.ts',
|
'!src/document/node/exclusive-group.ts',
|
||||||
'!**/node_modules/**',
|
'!**/node_modules/**',
|
||||||
'!**/vendor/**',
|
'!**/vendor/**',
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export class LiveEditing {
|
|||||||
const npm = node?.componentMeta?.npm;
|
const npm = node?.componentMeta?.npm;
|
||||||
const selected =
|
const selected =
|
||||||
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || '';
|
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || '';
|
||||||
editor?.emit('designer.builinSimulator.LiveEditing', {
|
editor?.emit('designer.builtinSimulator.liveEditing', {
|
||||||
selected,
|
selected,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Editor } from '@ali/lowcode-editor-core';
|
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 { LowCodePlugin } from './plugin';
|
||||||
import LowCodePluginContext from './plugin-context';
|
import LowCodePluginContext from './plugin-context';
|
||||||
import { getLogger, invariant } from '../utils';
|
import { getLogger, invariant } from '../utils';
|
||||||
@ -22,19 +22,23 @@ export class LowCodePluginManager implements ILowCodePluginManager {
|
|||||||
return new LowCodePluginContext(this.editor, this);
|
return new LowCodePluginContext(this.editor, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
register(
|
async register(
|
||||||
pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig,
|
pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig,
|
||||||
options?: CompositeObject,
|
pluginOptions?: any,
|
||||||
): void {
|
options?: LowCodeRegisterOptions,
|
||||||
|
): Promise<void> {
|
||||||
const ctx = this._getLowCodePluginContext();
|
const ctx = this._getLowCodePluginContext();
|
||||||
const config = pluginConfig(ctx, options);
|
const config = pluginConfigCreator(ctx, pluginOptions);
|
||||||
invariant(config.name, `${config.name} required`, config);
|
invariant(config.name, `${config.name} required`, config);
|
||||||
ctx.setLogger(config);
|
ctx.setLogger(config);
|
||||||
invariant(!this.pluginsMap.has(config.name), `${config.name} already exists`, this.pluginsMap.get(config.name));
|
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.plugins.push(plugin);
|
||||||
this.pluginsMap.set(plugin.name, 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 {
|
get(pluginName: string): ILowCodePlugin | undefined {
|
||||||
@ -51,7 +55,7 @@ export class LowCodePluginManager implements ILowCodePluginManager {
|
|||||||
|
|
||||||
async delete(pluginName: string): Promise<boolean> {
|
async delete(pluginName: string): Promise<boolean> {
|
||||||
const idx = this.plugins.findIndex(plugin => plugin.name === pluginName);
|
const idx = this.plugins.findIndex(plugin => plugin.name === pluginName);
|
||||||
if (idx < -1) return false;
|
if (idx === -1) return false;
|
||||||
const plugin = this.plugins[idx];
|
const plugin = this.plugins[idx];
|
||||||
await plugin.destroy();
|
await plugin.destroy();
|
||||||
|
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import {
|
|||||||
export interface ILowCodePluginConfig {
|
export interface ILowCodePluginConfig {
|
||||||
name: string;
|
name: string;
|
||||||
dep?: string[]; // 依赖插件名
|
dep?: string[]; // 依赖插件名
|
||||||
init(): void;
|
init?(): void;
|
||||||
destroy?(): void;
|
destroy?(): void;
|
||||||
exports?(): CompositeObject;
|
exports?(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILowCodePluginCore {
|
export interface ILowCodePluginCore {
|
||||||
@ -22,10 +22,10 @@ export interface ILowCodePluginCore {
|
|||||||
config: ILowCodePluginConfig;
|
config: ILowCodePluginConfig;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
on(event: string | symbol, listener: (...args: any[]) => void): any;
|
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;
|
emit(event: string | symbol, ...args: any[]): boolean;
|
||||||
removeAllListeners(event?: string | symbol): this;
|
removeAllListeners(event?: string | symbol): this;
|
||||||
init(): void;
|
init(forceInit?: boolean): void;
|
||||||
|
isInited(): boolean;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
toProxy(): any;
|
toProxy(): any;
|
||||||
setDisabled(flag: boolean): void;
|
setDisabled(flag: boolean): void;
|
||||||
@ -62,9 +62,10 @@ interface ILowCodePluginManagerPluginAccessor {
|
|||||||
|
|
||||||
export interface ILowCodePluginManagerCore {
|
export interface ILowCodePluginManagerCore {
|
||||||
register(
|
register(
|
||||||
pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig,
|
pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig,
|
||||||
|
pluginOptions?: any,
|
||||||
options?: CompositeObject,
|
options?: CompositeObject,
|
||||||
): void;
|
): Promise<void>;
|
||||||
get(pluginName: string): ILowCodePlugin | undefined;
|
get(pluginName: string): ILowCodePlugin | undefined;
|
||||||
getAll(): ILowCodePlugin[];
|
getAll(): ILowCodePlugin[];
|
||||||
has(pluginName: string): boolean;
|
has(pluginName: string): boolean;
|
||||||
@ -74,3 +75,7 @@ export interface ILowCodePluginManagerCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ILowCodePluginManager = ILowCodePluginManagerCore & ILowCodePluginManagerPluginAccessor;
|
export type ILowCodePluginManager = ILowCodePluginManagerCore & ILowCodePluginManagerPluginAccessor;
|
||||||
|
|
||||||
|
export type LowCodeRegisterOptions = {
|
||||||
|
autoInit?: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import {
|
|||||||
ILowCodePlugin,
|
ILowCodePlugin,
|
||||||
ILowCodePluginConfig,
|
ILowCodePluginConfig,
|
||||||
ILowCodePluginManager,
|
ILowCodePluginManager,
|
||||||
CompositeObject,
|
} from './plugin-types';
|
||||||
} from '@ali/lowcode-types';
|
import { CompositeObject } from '@ali/lowcode-types';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { getLogger, Logger, invariant } from '../utils';
|
import { getLogger, Logger, invariant } from '../utils';
|
||||||
|
|
||||||
@ -50,30 +50,36 @@ export class LowCodePlugin implements ILowCodePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
on(event: string | symbol, listener: (...args: any[]) => void): any {
|
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[]) {
|
emit(event: string | symbol, ...args: any[]) {
|
||||||
return this.emitter.emit(event, ...args);
|
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 {
|
removeAllListeners(event: string | symbol): any {
|
||||||
return this.emitter.removeAllListeners(event);
|
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');
|
this.logger.log('method init called');
|
||||||
await this.config.init?.call(undefined);
|
await this.config.init?.call(undefined);
|
||||||
this._inited = true;
|
this._inited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
|
if (!this._inited) return;
|
||||||
this.logger.log('method destroy called');
|
this.logger.log('method destroy called');
|
||||||
await this.config?.destroy?.call(undefined);
|
await this.config?.destroy?.call(undefined);
|
||||||
|
this._inited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisabled(flag = true) {
|
setDisabled(flag = true) {
|
||||||
@ -93,7 +99,7 @@ export class LowCodePlugin implements ILowCodePlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
async dispose() {
|
||||||
return this.manager.delete(this.name);
|
await this.manager.delete(this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
172
packages/designer/tests/plugin/plugin-manager.test.ts
Normal file
172
packages/designer/tests/plugin/plugin-manager.test.ts
Normal 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();
|
||||||
|
})
|
||||||
|
});
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ali/lowcode-designer": "^1.0.31",
|
"@ali/lowcode-designer": "^1.0.31",
|
||||||
"@ali/lowcode-editor-core": "^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-editor-skeleton": "^1.0.31",
|
||||||
"@ali/lowcode-plugin-designer": "^1.0.31",
|
"@ali/lowcode-plugin-designer": "^1.0.31",
|
||||||
"@ali/lowcode-plugin-outline-pane": "^1.0.31",
|
"@ali/lowcode-plugin-outline-pane": "^1.0.31",
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"@ali/lowcode-plugin-designer": "^1.0.31",
|
"@ali/lowcode-plugin-designer": "^1.0.31",
|
||||||
"@ali/lowcode-plugin-outline-pane": "^1.0.31",
|
"@ali/lowcode-plugin-outline-pane": "^1.0.31",
|
||||||
"@ali/lowcode-utils": "^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-i18n-util": "^2.0.0",
|
||||||
"@ali/ve-icons": "^4.1.9",
|
"@ali/ve-icons": "^4.1.9",
|
||||||
"@ali/ve-less-variables": "2.0.3",
|
"@ali/ve-less-variables": "2.0.3",
|
||||||
|
|||||||
@ -17,4 +17,3 @@ export * from './node';
|
|||||||
export * from './transform-stage';
|
export * from './transform-stage';
|
||||||
export * from './code-intermediate';
|
export * from './code-intermediate';
|
||||||
export * from './code-result';
|
export * from './code-result';
|
||||||
export * from './plugin';
|
|
||||||
|
|||||||
@ -180,8 +180,8 @@ export interface Callbacks {
|
|||||||
onMouseDownHook?: (e: MouseEvent, currentNode: any) => any;
|
onMouseDownHook?: (e: MouseEvent, currentNode: any) => any;
|
||||||
onDblClickHook?: (e: MouseEvent, currentNode: any) => any;
|
onDblClickHook?: (e: MouseEvent, currentNode: any) => any;
|
||||||
onClickHook?: (e: MouseEvent, currentNode: any) => any;
|
onClickHook?: (e: MouseEvent, currentNode: any) => any;
|
||||||
onLocateHook?: (e: any, currentNode: any) => any;
|
// onLocateHook?: (e: any, currentNode: any) => any;
|
||||||
onAcceptHook?: (currentNode: any, locationData: any) => any;
|
// onAcceptHook?: (currentNode: any, locationData: any) => any;
|
||||||
onMoveHook?: (currentNode: any) => boolean; // thinkof 限制性拖拽
|
onMoveHook?: (currentNode: any) => boolean; // thinkof 限制性拖拽
|
||||||
onChildMoveHook?: (childNode: any, currentNode: any) => boolean;
|
onChildMoveHook?: (childNode: any, currentNode: any) => boolean;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user