refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
268
packages/editor/editor-core/tests/EditorPluginManager.test.ts
Normal file
268
packages/editor/editor-core/tests/EditorPluginManager.test.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import {
|
||||
EditorPluginManager,
|
||||
UIRegistry,
|
||||
MessageHub,
|
||||
SerializerRegistry,
|
||||
EditorPluginCategory,
|
||||
PanelPosition
|
||||
} from '../src';
|
||||
import type { IEditorPlugin, ISerializer } from '../src';
|
||||
|
||||
class TestSerializer implements ISerializer<string> {
|
||||
serialize(data: string): Uint8Array {
|
||||
const encoder = new TextEncoder();
|
||||
return encoder.encode(data);
|
||||
}
|
||||
|
||||
deserialize(data: Uint8Array): string {
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(data);
|
||||
}
|
||||
|
||||
getSupportedType(): string {
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
|
||||
class TestEditorPlugin implements IEditorPlugin {
|
||||
readonly name = 'test-editor-plugin';
|
||||
readonly version = '1.0.0';
|
||||
readonly displayName = 'Test Editor Plugin';
|
||||
readonly category = EditorPluginCategory.Tool;
|
||||
readonly description = 'A test plugin for editor';
|
||||
|
||||
async install(): Promise<void> {
|
||||
// 测试安装
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
// 测试卸载
|
||||
}
|
||||
|
||||
registerMenuItems() {
|
||||
return [
|
||||
{
|
||||
id: 'test-menu',
|
||||
label: 'Test Menu',
|
||||
onClick: () => {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
registerToolbar() {
|
||||
return [
|
||||
{
|
||||
id: 'test-toolbar',
|
||||
label: 'Test Toolbar',
|
||||
groupId: 'test-group',
|
||||
onClick: () => {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
registerPanels() {
|
||||
return [
|
||||
{
|
||||
id: 'test-panel',
|
||||
title: 'Test Panel',
|
||||
position: PanelPosition.Left
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getSerializers() {
|
||||
return [new TestSerializer()];
|
||||
}
|
||||
}
|
||||
|
||||
describe('EditorPluginManager', () => {
|
||||
let core: Core;
|
||||
let pluginManager: EditorPluginManager;
|
||||
let uiRegistry: UIRegistry;
|
||||
let messageHub: MessageHub;
|
||||
let serializerRegistry: SerializerRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
core = Core.create({ debug: false });
|
||||
|
||||
uiRegistry = new UIRegistry();
|
||||
messageHub = new MessageHub();
|
||||
serializerRegistry = new SerializerRegistry();
|
||||
|
||||
Core.services.registerInstance(UIRegistry, uiRegistry);
|
||||
Core.services.registerInstance(MessageHub, messageHub);
|
||||
Core.services.registerInstance(SerializerRegistry, serializerRegistry);
|
||||
|
||||
pluginManager = new EditorPluginManager();
|
||||
pluginManager.initialize(core, Core.services);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
pluginManager.dispose();
|
||||
Core.destroy();
|
||||
});
|
||||
|
||||
describe('基本功能', () => {
|
||||
it('应该能够安装编辑器插件', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
expect(pluginManager.isInstalled(plugin.name)).toBe(true);
|
||||
expect(pluginManager.getEditorPlugin(plugin.name)).toBe(plugin);
|
||||
});
|
||||
|
||||
it('应该能够卸载编辑器插件', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
await pluginManager.uninstallEditor(plugin.name);
|
||||
|
||||
expect(pluginManager.isInstalled(plugin.name)).toBe(false);
|
||||
expect(pluginManager.getEditorPlugin(plugin.name)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该能够获取插件元数据', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const metadata = pluginManager.getPluginMetadata(plugin.name);
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata?.name).toBe(plugin.name);
|
||||
expect(metadata?.displayName).toBe(plugin.displayName);
|
||||
expect(metadata?.category).toBe(plugin.category);
|
||||
expect(metadata?.enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UI 注册', () => {
|
||||
it('应该注册菜单项', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const menu = uiRegistry.getMenu('test-menu');
|
||||
expect(menu).toBeDefined();
|
||||
expect(menu?.label).toBe('Test Menu');
|
||||
});
|
||||
|
||||
it('应该注册工具栏项', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const toolbar = uiRegistry.getToolbarItem('test-toolbar');
|
||||
expect(toolbar).toBeDefined();
|
||||
expect(toolbar?.label).toBe('Test Toolbar');
|
||||
});
|
||||
|
||||
it('应该注册面板', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const panel = uiRegistry.getPanel('test-panel');
|
||||
expect(panel).toBeDefined();
|
||||
expect(panel?.title).toBe('Test Panel');
|
||||
});
|
||||
|
||||
it('卸载插件时应该注销 UI 元素', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
await pluginManager.uninstallEditor(plugin.name);
|
||||
|
||||
expect(uiRegistry.getMenu('test-menu')).toBeUndefined();
|
||||
expect(uiRegistry.getToolbarItem('test-toolbar')).toBeUndefined();
|
||||
expect(uiRegistry.getPanel('test-panel')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('序列化器注册', () => {
|
||||
it('应该注册序列化器', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
expect(serializerRegistry.has(plugin.name, 'string')).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够使用序列化器', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const testData = 'Hello World';
|
||||
const serialized = serializerRegistry.serialize(plugin.name, 'string', testData);
|
||||
const deserialized = serializerRegistry.deserialize(plugin.name, 'string', serialized);
|
||||
|
||||
expect(deserialized).toBe(testData);
|
||||
});
|
||||
|
||||
it('卸载插件时应该注销序列化器', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
await pluginManager.uninstallEditor(plugin.name);
|
||||
|
||||
expect(serializerRegistry.has(plugin.name, 'string')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件通知', () => {
|
||||
it('应该发送插件安装事件', async () => {
|
||||
let eventReceived = false;
|
||||
|
||||
messageHub.subscribe('plugin:installed', (data: any) => {
|
||||
eventReceived = true;
|
||||
expect(data.name).toBe('test-editor-plugin');
|
||||
});
|
||||
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
});
|
||||
|
||||
it('应该发送项目打开事件', async () => {
|
||||
let eventReceived = false;
|
||||
|
||||
messageHub.subscribe('project:opened', (data: any) => {
|
||||
eventReceived = true;
|
||||
expect(data.path).toBe('/test/project');
|
||||
});
|
||||
|
||||
await pluginManager.notifyProjectOpen('/test/project');
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
});
|
||||
|
||||
it('应该发送项目关闭事件', async () => {
|
||||
let eventReceived = false;
|
||||
|
||||
messageHub.subscribe('project:closed', () => {
|
||||
eventReceived = true;
|
||||
});
|
||||
|
||||
await pluginManager.notifyProjectClose();
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('插件管理', () => {
|
||||
it('应该能够按类别获取插件', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
const plugins = pluginManager.getPluginsByCategory(EditorPluginCategory.Tool);
|
||||
expect(plugins.length).toBe(1);
|
||||
expect(plugins[0]).toBe(plugin);
|
||||
});
|
||||
|
||||
it('应该能够启用/禁用插件', async () => {
|
||||
const plugin = new TestEditorPlugin();
|
||||
await pluginManager.installEditor(plugin);
|
||||
|
||||
await pluginManager.disablePlugin(plugin.name);
|
||||
let metadata = pluginManager.getPluginMetadata(plugin.name);
|
||||
expect(metadata?.enabled).toBe(false);
|
||||
|
||||
await pluginManager.enablePlugin(plugin.name);
|
||||
metadata = pluginManager.getPluginMetadata(plugin.name);
|
||||
expect(metadata?.enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user