();
+
+ getDocument(id: string): DocumentModel | null {
+ // 此处不能使用 this.documentsMap.get(id),因为在乐高 rollback 场景,document.id 会被改成其他值
+ return this.documents.find(doc => doc.id === id) || null;
+ }
+
+ createDocument(data?: RootSchema): DocumentModel {
+ const doc = new DocumentModel(this, data || this?.data?.componentsTree?.[0]);
+ this.documents.push(doc);
+ this.documentsMap.set(doc.id, doc);
+ return doc;
+ }
+
+ open(doc?: string | DocumentModel | RootSchema): DocumentModel | null {
if (!doc) {
const got = this.documents.find((item) => item.isBlank());
if (got) {
return got.open();
}
- doc = new DocumentModel(this);
- this.documents.push(doc);
+ doc = this.createDocument();
return doc.open();
}
if (typeof doc === 'string') {
@@ -145,27 +193,24 @@ export class Project {
const data = this.data.componentsTree.find((data) => data.fileName === doc);
if (data) {
- doc = new DocumentModel(this, data);
- this.documents.push(doc);
+ doc = this.createDocument(data);
return doc.open();
}
- return;
+ return null;
}
if (isDocumentModel(doc)) {
return doc.open();
} else if (isPageSchema(doc)) {
- const foundDoc = this.documents.find(
- (curDoc) => curDoc?.rootNode?.id && curDoc?.rootNode?.id === doc?.id,
- );
- if (foundDoc) {
- foundDoc.remove();
- }
+ // 暂时注释掉,影响了 diff 功能
+ // const foundDoc = this.documents.find(curDoc => curDoc?.rootNode?.id && curDoc?.rootNode?.id === doc?.id);
+ // if (foundDoc) {
+ // foundDoc.remove();
+ // }
}
- doc = new DocumentModel(this, doc);
- this.documents.push(doc);
+ doc = this.createDocument(doc);
return doc.open();
}
@@ -186,13 +231,42 @@ export class Project {
});
}
+ /**
+ * 提供给模拟器的参数
+ */
+ @computed get simulatorProps(): object {
+ let simulatorProps = this.designer.simulatorProps;
+ if (typeof simulatorProps === 'function') {
+ simulatorProps = simulatorProps(this);
+ }
+ return {
+ ...simulatorProps,
+ project: this,
+ onMount: this.mountSimulator.bind(this),
+ };
+ }
+
+ private mountSimulator(simulator: ISimulatorHost) {
+ // TODO: 多设备 simulator 支持
+ this._simulator = simulator;
+ this.designer.editor.set('simulator', simulator);
+ }
+
+ setRendererReady(renderer: any) {
+ this.emitter.emit('lowcode_engine_renderer_ready', renderer);
+ }
+
+ onRendererReady(fn: (args: any) => void): () => void {
+ this.emitter.on('lowcode_engine_renderer_ready', fn);
+ return () => {
+ this.emitter.removeListener('lowcode_engine_renderer_ready', fn);
+ };
+ }
+
onCurrentDocumentChange(fn: (doc: DocumentModel) => void): () => void {
this.emitter.on('current-document.change', fn);
return () => {
this.emitter.removeListener('current-document.change', fn);
};
}
- // 通知标记删除,需要告知服务端
- // 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
- // 哪个删除就
}
diff --git a/packages/designer/src/simulator.ts b/packages/designer/src/simulator.ts
index 665f64a50..de95ae382 100644
--- a/packages/designer/src/simulator.ts
+++ b/packages/designer/src/simulator.ts
@@ -1,7 +1,7 @@
import { Component as ReactComponent, ComponentType } from 'react';
import { ComponentMetadata, NodeSchema } from '@ali/lowcode-types';
-import { ISensor, Point, ScrollTarget, IScrollable } from './designer';
-import { Node } from './document';
+import { ISensor, Point, ScrollTarget, IScrollable, LocateEvent, LocationData } from './designer';
+import { Node, ParentalNode } from './document';
export type AutoFit = '100%';
export const AutoFit = '100%';
@@ -60,6 +60,11 @@ export interface IViewport extends IScrollable {
toGlobalPoint(point: Point): Point;
}
+export interface DropContainer {
+ container: ParentalNode;
+ instance: ComponentInstance;
+}
+
/**
* 模拟器控制进程协议
*/
@@ -143,6 +148,8 @@ export interface ISimulatorHost extends ISensor {
findDOMNodes(instance: ComponentInstance, selector?: string): Array | null;
+ getDropContainer(e: LocateEvent): DropContainer | null;
+
/**
* 销毁
*/
@@ -154,6 +161,7 @@ export function isSimulatorHost(obj: any): obj is ISimulatorHost {
}
export interface NodeInstance {
+ docId: string;
nodeId: string;
instance: T;
node?: Node | null;
diff --git a/packages/designer/tests/__mocks__/document-model.ts b/packages/designer/tests/__mocks__/document-model.ts
new file mode 100644
index 000000000..982bb4c1b
--- /dev/null
+++ b/packages/designer/tests/__mocks__/document-model.ts
@@ -0,0 +1,10 @@
+export class DocumentModel {
+ a = 1;
+ c = {};
+ constructor() {
+ console.log('xxxxxxxxxxxxxxxxxxxx');
+ const b = { x: { y: 2 } };
+ const c: number = 2;
+ this.a = b?.x?.y;
+ }
+}
\ No newline at end of file
diff --git a/packages/designer/tests/__mocks__/node.ts b/packages/designer/tests/__mocks__/node.ts
new file mode 100644
index 000000000..4f225766b
--- /dev/null
+++ b/packages/designer/tests/__mocks__/node.ts
@@ -0,0 +1,9 @@
+export class Node2 {
+ a = 1;
+ c = {};
+ constructor() {
+ const b = { x: { y: 2 } };
+ const c: number = 2;
+ this.a = b?.x?.y;
+ }
+}
\ No newline at end of file
diff --git a/packages/designer/tests/bugs/misc.ts b/packages/designer/tests/bugs/misc.ts
new file mode 100644
index 000000000..e101d868f
--- /dev/null
+++ b/packages/designer/tests/bugs/misc.ts
@@ -0,0 +1,55 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+// import { Node } from '../../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+it.todo('在同一个节点下,相同名称的 slot 只能有一个', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ ids.forEach(id => {
+ expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
+ });
+
+ const exportSchema = currentDocument?.export(1);
+ expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
+ expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
+});
\ No newline at end of file
diff --git a/packages/designer/tests/builtin-simulator/host-view.test.tsx b/packages/designer/tests/builtin-simulator/host-view.test.tsx
new file mode 100644
index 000000000..53d2225e0
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/host-view.test.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import { Editor } from '@ali/lowcode-editor-core';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import TestRenderer from 'react-test-renderer';
+import { configure, render, mount } from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+import { BuiltinSimulatorHostView } from '../../src/builtin-simulator/host-view';
+
+configure({ adapter: new Adapter() });
+const editor = new Editor();
+
+describe('host-view 测试', () => {
+ let designer: Designer;
+ beforeEach(() => {
+ designer = new Designer({ editor });
+ });
+ afterEach(() => {
+ designer._componentMetasMap.clear();
+ designer = null;
+ });
+
+ it('host-view', () => {
+ const hostView = render();
+ })
+});
diff --git a/packages/designer/tests/builtin-simulator/host.test.tsx b/packages/designer/tests/builtin-simulator/host.test.tsx
new file mode 100644
index 000000000..fdeb0943a
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/host.test.tsx
@@ -0,0 +1,145 @@
+import React from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import { Editor } from '@ali/lowcode-editor-core';
+import {
+ AssetLevel,
+ Asset,
+ AssetList,
+ assetBundle,
+ assetItem,
+ AssetType,
+} from '@ali/lowcode-utils';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getMockDocument, getMockWindow, getMockEvent } from '../utils';
+import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
+import { eq } from 'lodash';
+
+const editor = new Editor();
+
+describe('host 测试', () => {
+ let designer: Designer;
+ beforeEach(() => {
+ designer = new Designer({ editor });
+ });
+ afterEach(() => {
+ designer._componentMetasMap.clear();
+ designer = null;
+ });
+
+ it('基础方法测试', async () => {
+ const host = new BuiltinSimulatorHost(designer.project);
+ expect(host.currentDocument).toBe(designer.project.currentDocument);
+ expect(host.renderEnv).toBe('default');
+ expect(host.device).toBe('default');
+ expect(host.deviceClassName).toBeUndefined;
+ host.setProps({
+ renderEnv: 'rax',
+ device: 'mobile',
+ deviceClassName: 'mobile-rocks',
+ componentsAsset: [{
+ type: AssetType.JSText,
+ content: 'console.log(1)',
+ }, {
+ type: AssetType.JSUrl,
+ content: '//path/to/js',
+ }],
+ theme: {
+ type: AssetType.CSSText,
+ content: '.theme {font-size: 50px;}',
+ }
+ });
+ expect(host.renderEnv).toBe('rax');
+ expect(host.device).toBe('mobile');
+ expect(host.deviceClassName).toBe('mobile-rocks');
+ expect(host.componentsAsset).toEqual([{
+ type: AssetType.JSText,
+ content: 'console.log(1)',
+ }, {
+ type: AssetType.JSUrl,
+ content: '//path/to/js',
+ }]);
+ expect(host.theme).toEqual({
+ type: AssetType.CSSText,
+ content: '.theme {font-size: 50px;}',
+ });
+ expect(host.componentsMap).toBe(designer.componentsMap);
+
+ host.set('renderEnv', 'vue');
+ expect(host.renderEnv).toBe('vue');
+
+ expect(host.getComponentContext).toThrow('Method not implemented.');
+ });
+
+ it('事件测试', async () => {
+ const host = new BuiltinSimulatorHost(designer.project);
+ const mockDocument = getMockDocument();
+ const mockWindow = getMockWindow(mockDocument);
+ const mockIframe = {
+ contentWindow: mockWindow,
+ contentDocument: mockDocument,
+ dispatchEvent() {},
+ };
+
+ // 非法分支测试
+ host.mountContentFrame();
+ expect(host._iframe).toBeUndefined();
+
+ host.set('library', [{
+ package: '@ali/vc-deep',
+ library: 'lib',
+ urls: ['a.js', 'b.js']
+ }]);
+
+ host.componentsConsumer.consume(() => {});
+ host.injectionConsumer.consume(() => {});
+ await host.mountContentFrame(mockIframe);
+
+ expect(host.contentWindow).toBe(mockWindow);
+
+ mockDocument.triggerEventListener(
+ 'mouseover',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'mouseleave',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'mousedown',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'mouseup',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'mousemove',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'click',
+ getMockEvent(document.createElement('input')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'dblclick',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ mockDocument.triggerEventListener(
+ 'contextmenu',
+ getMockEvent(mockDocument.createElement('div')),
+ host,
+ );
+ })
+});
diff --git a/packages/designer/tests/builtin-simulator/parse-metadata.test.ts b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts
new file mode 100644
index 000000000..50a1ba005
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts
@@ -0,0 +1,9 @@
+import '../fixtures/window';
+import { parseMetadata } from '../../src/builtin-simulator/utils/parse-metadata';
+
+describe('parseMetadata', () => {
+ it('parseMetadata', async () => {
+ const md1 = parseMetadata('Div');
+ const md2 = parseMetadata({ componentName: 'Div' });
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/builtin-simulator/path.test.ts b/packages/designer/tests/builtin-simulator/path.test.ts
new file mode 100644
index 000000000..6a15edbcf
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/path.test.ts
@@ -0,0 +1,78 @@
+import {
+ generateComponentName,
+ getNormalizedImportPath,
+ isPackagePath,
+ toTitleCase,
+ makeRelativePath,
+ removeVersion,
+ resolveAbsoluatePath,
+ joinPath,
+} from '../../src/builtin-simulator/utils/path';
+
+describe('builtin-simulator/utils/path 测试', () => {
+ it('isPackagePath', () => {
+ expect(isPackagePath('a')).toBeTruthy;
+ expect(isPackagePath('@ali/a')).toBeTruthy;
+ expect(isPackagePath('@alife/a')).toBeTruthy;
+ expect(isPackagePath('a.b')).toBeTruthy;
+ expect(isPackagePath('./a')).toBeFalsy;
+ expect(isPackagePath('../a')).toBeFalsy;
+ expect(isPackagePath('/a')).toBeFalsy;
+ });
+
+ it('toTitleCase', () => {
+ expect(toTitleCase('a')).toBe('A');
+ expect(toTitleCase('a_b')).toBe('AB');
+ expect(toTitleCase('a b')).toBe('AB');
+ expect(toTitleCase('a-b')).toBe('AB');
+ expect(toTitleCase('a.b')).toBe('AB');
+ expect(toTitleCase('a.b.cx')).toBe('ABCx');
+ });
+
+ it('generateComponentName', () => {
+ expect(generateComponentName('a/index.js')).toBe('A');
+ expect(generateComponentName('a_b/index.js')).toBe('AB');
+ expect(generateComponentName('a_b/index.web.js')).toBe('AB');
+ expect(generateComponentName('a_b/index.xxx.js')).toBe('AB');
+ expect(generateComponentName('a_b')).toBe('AB');
+ expect(generateComponentName('')).toBe('Component');
+ });
+
+ it('getNormalizedImportPath', () => {
+ expect(getNormalizedImportPath('/a')).toBe('/a');
+ expect(getNormalizedImportPath('/a/')).toBe('/a/');
+ expect(getNormalizedImportPath('/a/index.js')).toBe('/a');
+ expect(getNormalizedImportPath('/a/index.ts')).toBe('/a');
+ expect(getNormalizedImportPath('/a/index.jsx')).toBe('/a');
+ expect(getNormalizedImportPath('/a/index.tsx')).toBe('/a');
+ expect(getNormalizedImportPath('/a/index.x')).toBe('/a/index.x');
+ });
+
+ it('makeRelativePath', () => {
+ expect(makeRelativePath('/a/b/c', '/a/b')).toBe('c');
+ expect(makeRelativePath('a/b/c', '/a/c')).toBe('a/b/c');
+ expect(makeRelativePath('/a/b/c', '/a/c')).toBe('./b/c');
+ expect(makeRelativePath('/a/b/c', '/a/c/d')).toBe('../b/c');
+ });
+
+ it('resolveAbsoluatePath', () => {
+ expect(resolveAbsoluatePath('/a/b/c', '/a')).toBe('/a/b/c');
+ expect(resolveAbsoluatePath('@ali/fe', '/a')).toBe('@ali/fe');
+ expect(resolveAbsoluatePath('./a/b', '/c')).toBe('/c/a/b');
+ expect(resolveAbsoluatePath('./a/b/d', '/c')).toBe('/c/a/b/d');
+ expect(resolveAbsoluatePath('../a/b', '/c')).toBe('/a/b');
+ expect(resolveAbsoluatePath('../a/b/d', '/c')).toBe('/a/b/d');
+ expect(resolveAbsoluatePath('../../a', 'c')).toBe('../a');
+ });
+
+ it('joinPath', () => {
+ expect(joinPath('/a', 'b', 'c')).toBe('/a/b/c');
+ expect(joinPath('a', 'b', 'c')).toBe('./a/b/c');
+ });
+
+ it('removeVersion', () => {
+ expect(removeVersion('@ali/fe')).toBe('@ali/fe');
+ expect(removeVersion('@ali/fe@1.0.0/index')).toBe('@ali/fe/index');
+ expect(removeVersion('haha')).toBe('haha');
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/builtin-simulator/renderer.test.tsx b/packages/designer/tests/builtin-simulator/renderer.test.tsx
new file mode 100644
index 000000000..6cead4122
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/renderer.test.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import { Editor } from '@ali/lowcode-editor-core';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import TestRenderer from 'react-test-renderer';
+import { configure, render, mount } from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getMockRenderer } from '../utils';
+import { isSimulatorRenderer } from '../../src/builtin-simulator/renderer';
+
+
+describe('renderer 测试', () => {
+ it('renderer', () => {
+ expect(isSimulatorRenderer(getMockRenderer())).toBeTruthy;
+ })
+});
diff --git a/packages/designer/tests/builtin-simulator/throttle.test.ts b/packages/designer/tests/builtin-simulator/throttle.test.ts
new file mode 100644
index 000000000..fd2d7ce33
--- /dev/null
+++ b/packages/designer/tests/builtin-simulator/throttle.test.ts
@@ -0,0 +1,22 @@
+import '../fixtures/disable-raf';
+import { throttle } from '../../src/builtin-simulator/utils/throttle';
+
+const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
+
+const cb = jest.fn();
+
+describe('throttle', () => {
+ it('simple', async () => {
+ const fn = throttle(cb, 1000);
+ fn();
+
+ expect(cb).toBeCalledTimes(1);
+
+ await delay(200);
+ fn();
+
+ await delay(400);
+ fn();
+ expect(cb).toBeCalledTimes(1);
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/designer/builtin-hotkey.test.ts b/packages/designer/tests/designer/builtin-hotkey.test.ts
new file mode 100644
index 000000000..cc010f85c
--- /dev/null
+++ b/packages/designer/tests/designer/builtin-hotkey.test.ts
@@ -0,0 +1,202 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { Designer } from '../../src/designer/designer';
+import { Project } from '../../src/project/project';
+import formSchema from '../fixtures/schema/form';
+import '../../src/designer/builtin-hotkey';
+
+const editor = new Editor();
+
+let designer: Designer;
+beforeAll(() => {
+ globalContext.register(editor, Editor);
+});
+beforeEach(() => {
+ designer = new Designer({ editor });
+ designer.project.open(formSchema);
+});
+afterEach(() => {
+ designer = null;
+});
+
+// keyCode 对应表:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
+// hotkey 模块底层用的 keyCode,所以还不能用 key / code 测试
+describe('快捷键测试', () => {
+ it('right', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbj')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 39 });
+ document.dispatchEvent(event);
+
+ expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy;
+ });
+
+ it('left', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 37 });
+ document.dispatchEvent(event);
+
+ expect(designer.currentSelection?.selected.includes('node_k1ow3cbj')).toBeTruthy;
+ });
+
+ it('down', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 40 });
+ document.dispatchEvent(event);
+
+ expect(designer.currentSelection?.selected.includes('node_k1ow3cbm')).toBeTruthy;
+ });
+
+ it('up', () => {
+ const secondCardNode = designer.currentDocument?.getNode('node_k1ow3cbm')!;
+ secondCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 38 });
+ document.dispatchEvent(event);
+
+ expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy;
+ });
+
+ // 跟右侧节点调换位置
+ it('option + right', () => {
+ const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
+ firstButtonNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 39, altKey: true });
+ document.dispatchEvent(event);
+
+ expect(firstButtonNode.prevSibling?.getId()).toBe('node_k1ow3cbp');
+ });
+
+ // 跟左侧节点调换位置
+ it('option + left', () => {
+ const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ secondButtonNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 37, altKey: true });
+ document.dispatchEvent(event);
+
+ expect(secondButtonNode.nextSibling?.getId()).toBe('node_k1ow3cbn');
+ });
+
+ // 向父级移动该节点
+ it('option + up', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 38, altKey: true });
+ document.dispatchEvent(event);
+ });
+
+ // 将节点移入到兄弟节点中
+ it('option + up', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 40, altKey: true });
+ document.dispatchEvent(event);
+ });
+
+ // 撤销
+ it('command + z', async () => {
+ const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
+ let secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+
+ firstButtonNode.remove();
+ expect(secondButtonNode.getParent()?.children.size).toBe(1);
+
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
+ document.dispatchEvent(event);
+
+ // 重新获取一次节点,因为 documentModel.import 是全画布刷新
+ secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ expect(secondButtonNode.getParent()?.children.size).toBe(2);
+ });
+
+ // 重做
+ it('command + y', async () => {
+ const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
+ let secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+
+ firstButtonNode.remove();
+ expect(secondButtonNode.getParent()?.children.size).toBe(1);
+
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
+ document.dispatchEvent(event);
+
+ // 重新获取一次节点,因为 documentModel.import 是全画布刷新
+ secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ expect(secondButtonNode.getParent()?.children.size).toBe(2);
+
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ event = new KeyboardEvent('keydown', { keyCode: 89, metaKey: true });
+ document.dispatchEvent(event);
+
+ // 重新获取一次节点,因为 documentModel.import 是全画布刷新
+ secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ expect(secondButtonNode.getParent()?.children.size).toBe(1);
+ });
+
+ it('command + c', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ firstCardNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
+ document.dispatchEvent(event);
+ });
+
+ it('command + v', async () => {
+ const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ secondButtonNode.select();
+
+ let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
+ document.dispatchEvent(event);
+
+ event = new KeyboardEvent('keydown', { keyCode: 86, metaKey: true });
+ document.dispatchEvent(event);
+
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // clipboard 异步,先注释
+ // expect(secondButtonNode.getParent()?.children.size).toBe(3);
+ });
+
+ // 撤销所有选中
+ it('escape', () => {
+ const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ firstCardNode.select();
+
+ expect(designer.currentSelection!.selected.includes('node_k1ow3cbp')).toBeTruthy;
+
+ let event = new KeyboardEvent('keydown', { keyCode: 27 });
+ document.dispatchEvent(event);
+
+ expect(designer.currentSelection!.selected.length).toBe(0);
+ });
+
+ // 删除节点
+ it('delete', () => {
+ const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
+ const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
+ firstButtonNode.select();
+
+ expect(secondButtonNode.prevSibling.id).toBe('node_k1ow3cbn');
+
+ let event = new KeyboardEvent('keydown', { keyCode: 46 });
+ document.dispatchEvent(event);
+
+ expect(secondButtonNode.prevSibling).toBeNull;
+ });
+});
diff --git a/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts b/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts
new file mode 100644
index 000000000..8ddf63f0b
--- /dev/null
+++ b/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts
@@ -0,0 +1,105 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../../fixtures/window';
+import { Editor } from '@ali/lowcode-editor-core';
+import { Project } from '../../../src/project/project';
+import { Node } from '../../../src/document/node/node';
+import { Designer } from '../../../src/designer/designer';
+import formSchema from '../../../fixtures/schema/form';
+import settingSchema from '../../fixtures/schema/setting';
+import divMeta from '../../fixtures/prototype/div-meta';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
+
+const editor = new Editor();
+
+describe('setting-prop-entry 测试', () => {
+ let designer: Designer;
+ beforeEach(() => {
+ designer = new Designer({ editor });
+ });
+ afterEach(() => {
+ designer._componentMetasMap.clear();
+ designer = null;
+ });
+
+ describe('node 构造函数生成 settingEntry', () => {
+ it('常规方法测试', () => {
+ designer.createComponentMeta(divMeta);
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+
+ const { settingEntry } = divNode!;
+ const behaviorProp = settingEntry.getProp('behavior');
+ expect(behaviorProp.getProps()).toBe(settingEntry);
+ expect(behaviorProp.props).toBe(settingEntry);
+ expect(behaviorProp.getName()).toBe('behavior');
+ expect(behaviorProp.getKey()).toBe('behavior');
+ expect(behaviorProp.isIgnore()).toBeFalsy;
+ behaviorProp.setKey('behavior2');
+ expect(behaviorProp.getKey()).toBe('behavior2');
+ behaviorProp.setKey('behavior');
+ expect(behaviorProp.getValue()).toBe('NORMAL');
+ expect(behaviorProp.getMockOrValue()).toBe('NORMAL');
+
+ behaviorProp.setValue('LARGE');
+ expect(behaviorProp.getValue()).toBe('LARGE');
+ // behaviorProp.setPropValue('behavior', 'SMALL');
+ // expect(behaviorProp.getValue()).toBe('SMALL');
+ behaviorProp.setValue('NORMAL');
+ expect(behaviorProp.getValue()).toBe('NORMAL');
+
+ behaviorProp.clearValue();
+ behaviorProp.clearPropValue();
+ expect(settingEntry.getProp('behavior').getValue()).toBeUndefined;
+
+ behaviorProp.setValue('LARGE');
+ expect(behaviorProp.getValue()).toBe('LARGE');
+ behaviorProp.remove();
+ expect(settingEntry.getProp('behavior').getValue()).toBeUndefined;
+
+ expect(behaviorProp.getNode()).toBe(divNode);
+ expect(behaviorProp.getId().startsWith('entry')).toBeTruthy;
+ expect(behaviorProp.designer).toBe(designer);
+ expect(behaviorProp.isSingle).toBeTruthy;
+ expect(behaviorProp.isMultiple).toBeFalsy;
+ expect(behaviorProp.isGroup).toBeFalsy;
+ expect(behaviorProp.isSameComponent).toBeTruthy;
+ expect(typeof settingEntry.getValue).toBe('function');
+ settingEntry.getValue();
+
+ behaviorProp.setExtraPropValue('extraPropA', 'heihei');
+ expect(behaviorProp.getExtraPropValue('extraPropA', 'heihei'));
+ });
+
+ it.skip('type: group 场景测试', () => {
+
+ });
+
+ it('JSExpression 类型的 prop', () => {
+ designer.createComponentMeta(divMeta);
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+
+ const { settingEntry } = divNode!;
+ const customClassNameProp = settingEntry.getProp('customClassName');
+ expect(customClassNameProp.isUseVariable()).toBeTruthy;
+ expect(customClassNameProp.useVariable).toBeTruthy;
+
+ expect(customClassNameProp.getValue()).toEqual({
+ type: 'JSExpression',
+ value: 'getFromSomewhere()'
+ });
+ expect(customClassNameProp.getMockOrValue()).toBeUndefined;
+ expect(customClassNameProp.getVariableValue()).toBe('getFromSomewhere()');
+ customClassNameProp.setVariableValue('xxx');
+ expect(customClassNameProp.getVariableValue()).toBe('xxx');
+
+ const customClassName2Prop = settingEntry.getProp('customClassName2');
+ expect(customClassName2Prop.getMockOrValue()).toEqual({
+ hi: 'mock',
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts b/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts
new file mode 100644
index 000000000..e7e5dab24
--- /dev/null
+++ b/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts
@@ -0,0 +1,174 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../../fixtures/window';
+import { Editor } from '@ali/lowcode-editor-core';
+import { Project } from '../../../src/project/project';
+import { Node } from '../../../src/document/node/node';
+import { Designer } from '../../../src/designer/designer';
+import formSchema from '../../fixtures/schema/form';
+import settingSchema from '../../fixtures/schema/setting';
+import divMeta from '../../fixtures/prototype/div-meta';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
+
+const editor = new Editor();
+
+describe('setting-top-entry 测试', () => {
+ let designer: Designer;
+ beforeEach(() => {
+ designer = new Designer({ editor });
+ });
+ afterEach(() => {
+ designer._componentMetasMap.clear();
+ designer = null;
+ });
+
+ describe('node 构造函数生成 settingEntry', () => {
+ it('常规方法测试', () => {
+ designer.createComponentMeta(divMeta);
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+
+ const { settingEntry } = divNode!;
+ expect(settingEntry.getPropValue('behavior')).toBe('NORMAL');
+ expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL');
+ settingEntry.setPropValue('behavior', 'LARGE');
+ expect(settingEntry.getPropValue('behavior')).toBe('LARGE');
+ expect(settingEntry.get('behavior').getValue()).toBe('LARGE');
+ settingEntry.getProp('behavior').setValue('SMALL');
+ expect(settingEntry.getPropValue('behavior')).toBe('SMALL');
+ settingEntry.clearPropValue('behavior');
+ expect(settingEntry.getPropValue('behavior')).toBeUndefined;
+
+ expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o');
+ settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new');
+ expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new');
+
+ expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha');
+ settingEntry.setExtraPropValue('extraPropA', 'haha2');
+ expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2');
+
+ settingEntry.mergeProps({
+ newPropA: 'haha',
+ });
+ expect(settingEntry.getPropValue('newPropA')).toBe('haha');
+ settingEntry.setProps({
+ newPropB: 'haha',
+ });
+ expect(settingEntry.getPropValue('newPropB')).toBe('haha');
+ settingEntry.setValue({
+ newPropC: 'haha',
+ });
+ expect(settingEntry.getPropValue('newPropC')).toBe('haha');
+
+ expect(settingEntry.getPage()).toBe(currentDocument);
+ expect(settingEntry.getNode()).toBe(divNode);
+ expect(settingEntry.node).toBe(divNode);
+ expect(settingEntry.getId()).toBe('div');
+ expect(settingEntry.first).toBe(divNode);
+ expect(settingEntry.designer).toBe(designer);
+ expect(settingEntry.isSingle).toBeTruthy;
+ expect(settingEntry.isMultiple).toBeFalsy;
+ expect(settingEntry.isSameComponent).toBeTruthy;
+
+ expect(typeof settingEntry.getValue).toBe('function');
+ settingEntry.getValue();
+ });
+
+ it('清理方法测试', () => {
+ designer.createComponentMeta(divMeta);
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+
+ const { settingEntry } = divNode!;
+ expect(settingEntry.items).toHaveLength(3);
+ settingEntry.purge();
+ expect(settingEntry.items).toHaveLength(0);
+ });
+
+ it('vision 兼容测试', () => {
+ designer.createComponentMeta(divMeta);
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+
+ console.log(divNode?.getPropValue('behavior'));
+ const { settingEntry } = divNode!;
+
+ expect(typeof settingEntry.getChildren).toBe('function');
+ expect(typeof settingEntry.getDOMNode).toBe('function');
+ expect(typeof settingEntry.getStatus).toBe('function');
+ expect(typeof settingEntry.setStatus).toBe('function');
+ settingEntry.getStatus();
+ settingEntry.setStatus();
+ settingEntry.getChildren();
+ settingEntry.getDOMNode();
+ });
+
+ it('没有 node', () => {
+ const create1 = designer.createSettingEntry.bind(designer);
+ const create2 = designer.createSettingEntry.bind(designer, []);
+ expect(create1).toThrowError('nodes should not be empty');
+ expect(create2).toThrowError('nodes should not be empty');
+ });
+ });
+
+ describe('designer.createSettingEntry 生成 settingEntry(多 node 场景)', () => {
+ it('相同类型的 node', () => {
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+ const divNode2 = currentDocument?.getNode('div2');
+ const settingEntry = designer.createSettingEntry([divNode, divNode2]);
+
+ expect(settingEntry.isMultiple).toBeTruthy;
+ expect(settingEntry.isSameComponent).toBeTruthy;
+ expect(settingEntry.isSingle).toBeFalsy;
+
+ expect(settingEntry.getPropValue('behavior')).toBe('NORMAL');
+ expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL');
+ settingEntry.setPropValue('behavior', 'LARGE');
+ expect(settingEntry.getPropValue('behavior')).toBe('LARGE');
+ expect(settingEntry.get('behavior').getValue()).toBe('LARGE');
+ // 多个 node 都被成功设值
+ expect(divNode?.getPropValue('behavior')).toBe('LARGE');
+ expect(divNode2?.getPropValue('behavior')).toBe('LARGE');
+
+ settingEntry.getProp('behavior').setValue('SMALL');
+ expect(settingEntry.getPropValue('behavior')).toBe('SMALL');
+ // 多个 node 都被成功设值
+ expect(divNode?.getPropValue('behavior')).toBe('SMALL');
+ expect(divNode2?.getPropValue('behavior')).toBe('SMALL');
+
+ settingEntry.clearPropValue('behavior');
+ expect(settingEntry.getPropValue('behavior')).toBeUndefined;
+ // 多个 node 都被成功设值
+ expect(divNode?.getPropValue('behavior')).toBeUndefined;
+ expect(divNode2?.getPropValue('behavior')).toBeUndefined;
+
+ expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o');
+ settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new');
+ expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new');
+
+ expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha');
+ settingEntry.setExtraPropValue('extraPropA', 'haha2');
+ expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2');
+ });
+
+ it('不同类型的 node', () => {
+ designer.project.open(settingSchema);
+ const { currentDocument } = designer.project;
+ const divNode = currentDocument?.getNode('div');
+ const testNode = currentDocument?.getNode('test');
+ const settingEntry = designer.createSettingEntry([divNode, testNode]);
+
+ expect(settingEntry.isMultiple).toBeTruthy;
+ expect(settingEntry.isSameComponent).toBeFalsy;
+ expect(settingEntry.isSingle).toBeFalsy;
+
+ // 不同类型的 node 场景下,理论上从页面上已没有修改属性的方法调用,所以此处不再断言各设值方法
+ // 思考:假如以后面向其他场景,比如用户用 API 强行调用,是否需要做健壮性保护?
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/document/document-model/document-model.test.ts b/packages/designer/tests/document/document-model/document-model.test.ts
new file mode 100644
index 000000000..94eda1871
--- /dev/null
+++ b/packages/designer/tests/document/document-model/document-model.test.ts
@@ -0,0 +1,16 @@
+import '../../fixtures/window';
+window.matchMedia('width=600px');
+import { DocumentModel } from '../../../src/document/document-model';
+// const { DocumentModel } = require('../../../src/document/document-model');
+// const { Node } = require('../__mocks__/node');
+
+describe.skip('basic utility', () => {
+ test('delegateMethod - useOriginMethodName', () => {
+
+ const node = new DocumentModel({}, {
+ componentName: 'Component',
+ });
+ console.log(node);
+ expect(1).toBe(1);
+ });
+});
diff --git a/packages/designer/tests/document/document-model/node.test.ts b/packages/designer/tests/document/document-model/node.test.ts
new file mode 100644
index 000000000..29ea34123
--- /dev/null
+++ b/packages/designer/tests/document/document-model/node.test.ts
@@ -0,0 +1,28 @@
+import '../../fixtures/window';
+import { DocumentModel } from '../../../src/document/document-model';
+import { Node } from '../../../src/document/node/node';
+// import { Node2 } from './__mocks__/node';
+
+jest.mock('../../../src/document/document-model', () => {
+ return {
+ DocumentModel: jest.fn().mockImplementation(() => {
+ return {
+ project: {
+ designer: { createSettingEntry() {}, transformProps() {} },
+ getSchema() {},
+ },
+ nextId() {},
+ };
+ }),
+ };
+});
+
+describe.skip('basic utility', () => {
+ test('delegateMethod - useOriginMethodName', () => {
+ const dm = new DocumentModel({} as any, {} as any);
+ console.log(dm.nextId);
+ const node = new Node(dm, { componentName: 'Leaf' });
+ console.log(node);
+ expect(1).toBe(1);
+ });
+});
diff --git a/packages/designer/tests/document/selection.test.ts b/packages/designer/tests/document/selection.test.ts
new file mode 100644
index 000000000..4d81e935e
--- /dev/null
+++ b/packages/designer/tests/document/selection.test.ts
@@ -0,0 +1,245 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('选择区测试', () => {
+ it('常规方法', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+
+ selection.select('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selection.selected).toEqual(['form']);
+ selectionChangeHandler.mockClear();
+
+ selection.select('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
+ expect(selection.selected).toEqual(['form']);
+
+ selection.select('node_k1ow3cbj');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cbj']);
+ expect(selection.selected).toEqual(['node_k1ow3cbj']);
+ selectionChangeHandler.mockClear();
+
+ selection.selectAll(['node_k1ow3cbj', 'form']);
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cbj', 'form']);
+ expect(selection.selected).toEqual(['node_k1ow3cbj', 'form']);
+ selectionChangeHandler.mockClear();
+
+ selection.remove('node_k1ow3cbj');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
+ expect(selection.selected).toEqual(['form']);
+ selectionChangeHandler.mockClear();
+
+ selection.clear();
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual([]);
+ expect(selection.selected).toEqual([]);
+ selectionChangeHandler.mockClear();
+
+ // 无选中时调用 clear,不再触发事件
+ selection.clear();
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
+ expect(selection.selected).toEqual([]);
+ selectionChangeHandler.mockClear();
+ });
+
+ it('add 方法', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+
+ selection.add('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
+ expect(selection.selected).toEqual(['form']);
+ selectionChangeHandler.mockClear();
+
+ // 再加一次相同的节点,不触发事件
+ selection.add('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
+ expect(selection.selected).toEqual(['form']);
+ selectionChangeHandler.mockClear();
+
+ selection.add('form2');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'form2']);
+ expect(selection.selected).toEqual(['form', 'form2']);
+ selectionChangeHandler.mockClear();
+ });
+
+ it('dispose 方法', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+
+ selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
+
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+ selection.dispose();
+
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'node_k1ow3cbj']);
+ expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
+ selectionChangeHandler.mockClear();
+ });
+
+ it('dispose 方法', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+
+ selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
+
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+ selection.dispose();
+
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'node_k1ow3cbj']);
+ expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
+ selectionChangeHandler.mockClear();
+ });
+
+ it('containsNode 方法', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+
+ selection.select('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
+ expect(selection.selected).toEqual(['form']);
+ expect(selection.has('form')).toBe(true);
+ expect(selection.containsNode(currentDocument?.getNode('form'))).toBe(true);
+ expect(selection.containsNode(currentDocument?.getNode('node_k1ow3cbj'))).toBe(true);
+ expect(selection.containsNode(currentDocument?.getNode('node_k1ow3cb9'))).toBe(false);
+ expect(selection.getNodes()).toEqual([currentDocument?.getNode('form')]);
+ selectionChangeHandler.mockClear();
+
+ selection.add('node_k1ow3cbj');
+ expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
+ expect(selection.getTopNodes()).toEqual([currentDocument?.getNode('form')]);
+ expect(selection.getTopNodes(true)).toEqual([currentDocument?.getNode('form')]);
+ });
+
+ it('containsNode 方法 - excludeRoot: true', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+ const selectionChangeHandler = jest.fn();
+ selection.onSelectionChange(selectionChangeHandler);
+
+ selection.select('node_k1ow3cb9');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cb9']);
+ expect(selection.selected).toEqual(['node_k1ow3cb9']);
+ expect(selection.has('node_k1ow3cb9')).toBe(true);
+ expect(selection.containsNode(currentDocument?.getNode('form'))).toBe(true);
+ expect(selection.containsNode(currentDocument?.getNode('form'), true)).toBe(false);
+ selectionChangeHandler.mockClear();
+ });
+
+ it('containsNode 方法 - excludeRoot: true', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap, selection } = currentDocument!;
+ const selectionChangeHandler = jest.fn();
+ const dispose = selection.onSelectionChange(selectionChangeHandler);
+
+ selection.select('form');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
+ expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
+ selectionChangeHandler.mockClear();
+
+ // dispose 后,selected 会被赋值,但是变更事件不会被触发
+ dispose();
+ selection.select('node_k1ow3cb9');
+ expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
+ expect(selection.selected).toEqual(['node_k1ow3cb9']);
+ selectionChangeHandler.mockClear();
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/fixtures/component-metadata/div.ts b/packages/designer/tests/fixtures/component-metadata/div.ts
new file mode 100644
index 000000000..395cb58bb
--- /dev/null
+++ b/packages/designer/tests/fixtures/component-metadata/div.ts
@@ -0,0 +1,275 @@
+export default {
+ componentName: 'Div',
+ title: '容器',
+ docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ devMode: 'procode',
+ tags: ['布局'],
+ configure: {
+ props: [
+ {
+ type: 'field',
+ name: 'behavior',
+ title: '默认状态',
+ extraProps: {
+ display: 'inline',
+ defaultValue: 'NORMAL',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: '__style__',
+ title: {
+ label: '样式设置',
+ tip: '点击 ? 查看样式设置器用法指南',
+ docUrl: 'https://lark.alipay.com/legao/help/design-tool-style',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {},
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ advanced: true,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'group',
+ name: 'groupkgzzeo41',
+ title: '高级',
+ extraProps: {
+ display: 'accordion',
+ },
+ items: [
+ {
+ type: 'field',
+ name: 'fieldId',
+ title: {
+ label: '唯一标识',
+ },
+ extraProps: {
+ display: 'block',
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: '请输入唯一标识',
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'useFieldIdAsDomId',
+ title: {
+ label: '将唯一标识用作 DOM ID',
+ },
+ extraProps: {
+ display: 'block',
+ defaultValue: false,
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {},
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'customClassName',
+ title: '自定义样式类',
+ extraProps: {
+ display: 'block',
+ defaultValue: '',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: null,
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: 'events',
+ title: {
+ label: '动作设置',
+ tip: '点击 ? 查看如何设置组件的事件响应动作',
+ docUrl: 'https://lark.alipay.com/legao/legao/events-call',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ events: [
+ {
+ name: 'onClick',
+ title: '当点击时',
+ initialValue:
+ "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
+ },
+ {
+ name: 'onMouseEnter',
+ title: '当鼠标进入时',
+ initialValue:
+ "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
+ },
+ {
+ name: 'onMouseLeave',
+ title: '当鼠标离开时',
+ initialValue:
+ "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
+ },
+ ],
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'onClick',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseEnter',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseLeave',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ ],
+ },
+ ],
+ component: {
+ isContainer: true,
+ nestingRule: {
+ parentWhitelist: 'Div',
+ childWhitelist: 'Div',
+ },
+ },
+ supports: {},
+ },
+ experimental: {
+ callbacks: {},
+ initials: [
+ {
+ name: 'behavior',
+ },
+ {
+ name: '__style__',
+ },
+ {
+ name: 'fieldId',
+ },
+ {
+ name: 'useFieldIdAsDomId',
+ },
+ {
+ name: 'customClassName',
+ },
+ {
+ name: 'events',
+ },
+ {
+ name: 'onClick',
+ },
+ {
+ name: 'onMouseEnter',
+ },
+ {
+ name: 'onMouseLeave',
+ },
+ ],
+ filters: [
+ {
+ name: 'events',
+ },
+ {
+ name: 'onClick',
+ },
+ {
+ name: 'onMouseEnter',
+ },
+ {
+ name: 'onMouseLeave',
+ },
+ ],
+ autoruns: [],
+ },
+};
diff --git a/packages/designer/tests/fixtures/disable-raf.ts b/packages/designer/tests/fixtures/disable-raf.ts
new file mode 100644
index 000000000..14b710125
--- /dev/null
+++ b/packages/designer/tests/fixtures/disable-raf.ts
@@ -0,0 +1,3 @@
+Object.defineProperty(window, 'requestAnimationFrame', {
+ value: null,
+})
\ No newline at end of file
diff --git a/packages/designer/tests/fixtures/prototype/div-meta.ts b/packages/designer/tests/fixtures/prototype/div-meta.ts
new file mode 100644
index 000000000..a2b410494
--- /dev/null
+++ b/packages/designer/tests/fixtures/prototype/div-meta.ts
@@ -0,0 +1,259 @@
+export default {
+ componentName: 'Div',
+ title: '容器',
+ docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ devMode: 'procode',
+ tags: ['布局'],
+ configure: {
+ props: [
+ {
+ type: 'field',
+ name: 'behavior',
+ title: '默认状态',
+ extraProps: {
+ display: 'inline',
+ defaultValue: 'NORMAL',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: '__style__',
+ title: {
+ label: '样式设置',
+ tip: '点击 ? 查看样式设置器用法指南',
+ docUrl: 'https://lark.alipay.com/legao/help/design-tool-style',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {},
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ advanced: true,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'group',
+ name: 'groupkh97h5kc',
+ title: '高级',
+ extraProps: {
+ display: 'accordion',
+ },
+ items: [
+ {
+ type: 'field',
+ name: 'fieldId',
+ title: {
+ label: '唯一标识',
+ },
+ extraProps: {
+ display: 'block',
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: '请输入唯一标识',
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'useFieldIdAsDomId',
+ title: {
+ label: '将唯一标识用作 DOM ID',
+ },
+ extraProps: {
+ display: 'block',
+ defaultValue: false,
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {},
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'customClassName',
+ title: '自定义样式类',
+ extraProps: {
+ display: 'block',
+ defaultValue: '',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: null,
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: 'events',
+ title: {
+ label: '动作设置',
+ tip: '点击 ? 查看如何设置组件的事件响应动作',
+ docUrl: 'https://lark.alipay.com/legao/legao/events-call',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ events: [
+ {
+ name: 'onClick',
+ title: '当点击时',
+ initialValue:
+ "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
+ },
+ {
+ name: 'onMouseEnter',
+ title: '当鼠标进入时',
+ initialValue:
+ "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
+ },
+ {
+ name: 'onMouseLeave',
+ title: '当鼠标离开时',
+ initialValue:
+ "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
+ },
+ ],
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'onClick',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseEnter',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseLeave',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ ],
+ },
+ ],
+ component: {
+ isContainer: true,
+ nestingRule: {},
+ },
+ supports: {},
+ },
+ experimental: {
+ callbacks: {},
+ initials: [
+ {
+ name: 'behavior',
+ },
+ {
+ name: '__style__',
+ },
+ {
+ name: 'fieldId',
+ },
+ {
+ name: 'useFieldIdAsDomId',
+ },
+ {
+ name: 'customClassName',
+ },
+ {
+ name: 'events',
+ },
+ {
+ name: 'onClick',
+ },
+ {
+ name: 'onMouseEnter',
+ },
+ {
+ name: 'onMouseLeave',
+ },
+ ],
+ filters: [],
+ autoruns: [],
+ },
+};
diff --git a/packages/designer/tests/fixtures/schema/form.ts b/packages/designer/tests/fixtures/schema/form.ts
new file mode 100644
index 000000000..903b21756
--- /dev/null
+++ b/packages/designer/tests/fixtures/schema/form.ts
@@ -0,0 +1,983 @@
+export default {
+ componentName: 'Page',
+ id: 'node_k1ow3cb9',
+ title: 'hey, i\' a page!',
+ props: {
+ extensions: {
+ 启用页头: true,
+ },
+ pageStyle: {
+ backgroundColor: '#f2f3f5',
+ },
+ containerStyle: {},
+ className: 'page_kgaqfbm4',
+ templateVersion: '1.0.0',
+ },
+ lifeCycles: {
+ constructor: {
+ type: 'js',
+ compiled:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ source:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ },
+ },
+ condition: true,
+ css:
+ 'body{background-color:#f2f3f5}.card_kgaqfbm5 {\n margin-bottom: 12px;\n}.card_kgaqfbm6 {\n margin-bottom: 12px;\n}.button_kgaqfbm7 {\n margin-right: 16px;\n width: 80px\n}.button_kgaqfbm8 {\n width: 80px;\n}.div_kgaqfbm9 {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ methods: {
+ __initMethods__: {
+ type: 'js',
+ source: 'function (exports, module) { /*set actions code here*/ }',
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
+ },
+ },
+ dataSource: {
+ offline: [],
+ globalConfig: {
+ fit: {
+ compiled: '',
+ source: '',
+ type: 'js',
+ error: {},
+ },
+ },
+ online: [],
+ sync: true,
+ list: [],
+ },
+ children: [
+ {
+ componentName: 'RootHeader',
+ id: 'node_k1ow3cba',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'PageHeader',
+ id: 'node_k1ow3cbd',
+ props: {
+ extraContent: '',
+ __slot__extraContent: false,
+ __slot__action: false,
+ title: {
+ // type: 'JSSlot',
+ value: [
+ {
+ componentName: 'Text',
+ id: 'node_k1ow3cbf',
+ props: {
+ showTitle: false,
+ behavior: 'NORMAL',
+ content: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '个人信息',
+ type: 'i18n',
+ },
+ __style__: {},
+ fieldId: 'text_k1ow3h1j',
+ maxLine: 0,
+ },
+ condition: true,
+ },
+ ],
+ },
+ content: '',
+ __slot__logo: false,
+ __slot__crumb: false,
+ crumb: '',
+ tab: '',
+ logo: '',
+ action: '',
+ __slot__tab: false,
+ __style__: {},
+ __slot__content: false,
+ fieldId: 'pageHeader_k1ow3h1i',
+ subTitle: false,
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'RootContent',
+ id: 'node_k1ow3cbb',
+ props: {
+ contentBgColor: 'transparent',
+ contentPadding: '0',
+ contentMargin: '20',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Form',
+ id: 'form',
+ extraPropA: 'extraPropA',
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ obj: {
+ a: 1,
+ b: false,
+ c: 'string',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ slotA: '',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Card',
+ id: 'node_k1ow3cbj',
+ props: {
+ __slot__title: false,
+ subTitle: {
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ __slot__subTitle: false,
+ extra: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ className: 'card_kgaqfbm5',
+ title: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '基本信息',
+ type: 'i18n',
+ },
+ __slot__extra: false,
+ showHeadDivider: true,
+ __style__: ':root {\n margin-bottom: 12px;\n}',
+ showTitleBullet: true,
+ contentHeight: '',
+ fieldId: 'card_k1ow3h1l',
+ dividerNoInset: false,
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'CardContent',
+ id: 'node_k1ow3cbk',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'ColumnsLayout',
+ id: 'node_k1ow3cbw',
+ props: {
+ layout: '6:6',
+ columnGap: '20',
+ rowGap: 0,
+ __style__: {},
+ fieldId: 'columns_k1ow3h1v',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cbx',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjm',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cbz',
+ props: {
+ fieldName: 'name',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [
+ {
+ type: 'required',
+ },
+ ],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1w',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '姓名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc1',
+ props: {
+ fieldName: 'englishName',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1y',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '英文名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc3',
+ props: {
+ fieldName: 'jobTitle',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h20',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '职位',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cby',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjn',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc2',
+ props: {
+ fieldName: 'nickName',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1z',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '花名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ {
+ componentName: 'SelectField',
+ id: 'node_k1ow3cc0',
+ props: {
+ fieldName: 'gender',
+ hasClear: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ mode: 'single',
+ showSearch: false,
+ autoWidth: true,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please select',
+ zh_CN: '请选择',
+ type: 'i18n',
+ },
+ hasBorder: true,
+ behavior: 'NORMAL',
+ value: '',
+ validation: [
+ {
+ type: 'required',
+ },
+ ],
+ __style__: {},
+ fieldId: 'select_k1ow3h1x',
+ notFoundContent: {
+ use: 'zh_CN',
+ type: 'i18n',
+ },
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'SelectField',
+ zh_CN: '性别',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ wrapperColOffset: 0,
+ hasSelectAll: false,
+ hasArrow: true,
+ size: 'medium',
+ labelAlign: 'top',
+ filterLocal: true,
+ dataSource: [
+ {
+ defaultChecked: false,
+ text: {
+ en_US: 'Option 1',
+ zh_CN: '男',
+ type: 'i18n',
+ __sid__: 'param_k1owc4tb',
+ },
+ __sid__: 'serial_k1owc4t1',
+ value: 'M',
+ sid: 'opt_k1owc4t2',
+ },
+ {
+ defaultChecked: false,
+ text: {
+ en_US: 'Option 2',
+ zh_CN: '女',
+ type: 'i18n',
+ __sid__: 'param_k1owc4tf',
+ },
+ __sid__: 'serial_k1owc4t2',
+ value: 'F',
+ sid: 'opt_k1owc4t3',
+ },
+ ],
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ searchDelay: 300,
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'Card',
+ id: 'node_k1ow3cbl',
+ props: {
+ __slot__title: false,
+ subTitle: {
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ __slot__subTitle: false,
+ extra: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ className: 'card_kgaqfbm6',
+ title: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '部门信息',
+ type: 'i18n',
+ },
+ __slot__extra: false,
+ showHeadDivider: true,
+ __style__: ':root {\n margin-bottom: 12px;\n}',
+ showTitleBullet: true,
+ contentHeight: '',
+ fieldId: 'card_k1ow3h1m',
+ dividerNoInset: false,
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'CardContent',
+ id: 'node_k1ow3cbm',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc4',
+ props: {
+ fieldName: 'department',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h21',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '所属部门',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ {
+ componentName: 'ColumnsLayout',
+ id: 'node_k1ow3cc5',
+ props: {
+ layout: '6:6',
+ columnGap: '20',
+ rowGap: 0,
+ __style__: {},
+ fieldId: 'columns_k1ow3h22',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cc6',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjo',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc8',
+ props: {
+ fieldName: 'leader',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h23',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '主管',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cc7',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjp',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc9',
+ props: {
+ fieldName: 'hrg',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h24',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: 'HRG',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: null,
+ zh_CN: '',
+ },
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'Div',
+ id: 'node_k1ow3cbo',
+ props: {
+ className: 'div_kgaqfbm9',
+ behavior: 'NORMAL',
+ __style__:
+ ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ events: {},
+ fieldId: 'div_k1ow3h1o',
+ useFieldIdAsDomId: false,
+ customClassName: '',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_k1ow3cbn',
+ props: {
+ triggerEventsWhenLoading: false,
+ onClick: {
+ rawType: 'events',
+ type: 'JSExpression',
+ value: 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])',
+ events: [
+ {
+ name: 'submit',
+ id: 'submit',
+ params: {},
+ type: 'actionRef',
+ uuid: '1570966253282_0',
+ },
+ ],
+ },
+ size: 'medium',
+ baseIcon: '',
+ otherIcon: '',
+ className: 'button_kgaqfbm7',
+ type: 'primary',
+ behavior: 'NORMAL',
+ loading: false,
+ content: {
+ use: 'zh_CN',
+ en_US: 'Button',
+ zh_CN: '提交',
+ type: 'i18n',
+ },
+ __style__: ':root {\n margin-right: 16px;\n width: 80px\n}',
+ fieldId: 'button_k1ow3h1n',
+ },
+ condition: true,
+ },
+ {
+ componentName: 'Button',
+ id: 'node_k1ow3cbp',
+ props: {
+ triggerEventsWhenLoading: false,
+ size: 'medium',
+ baseIcon: '',
+ otherIcon: '',
+ className: 'button_kgaqfbm8',
+ type: 'normal',
+ behavior: 'NORMAL',
+ loading: false,
+ content: {
+ use: 'zh_CN',
+ en_US: 'Button',
+ zh_CN: '取消',
+ type: 'i18n',
+ },
+ __style__: ':root {\n width: 80px;\n}',
+ fieldId: 'button_k1ow3h1p',
+ greeting: {
+ // type: 'JSSlot',
+ value: [{
+ componentName: 'Text',
+ props: {},
+ }]
+ }
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'RootFooter',
+ id: 'node_k1ow3cbc',
+ props: {},
+ condition: true,
+ },
+ ],
+};
diff --git a/packages/designer/tests/fixtures/schema/setting.ts b/packages/designer/tests/fixtures/schema/setting.ts
new file mode 100644
index 000000000..a0d45d0d9
--- /dev/null
+++ b/packages/designer/tests/fixtures/schema/setting.ts
@@ -0,0 +1,90 @@
+export default {
+ componentName: 'Page',
+ id: 'node_k1ow3cb9',
+ title: 'hey, i\' a page!',
+ props: {
+ extensions: {
+ 启用页头: true,
+ },
+ pageStyle: {
+ backgroundColor: '#f2f3f5',
+ },
+ containerStyle: {},
+ className: 'page_kgaqfbm4',
+ templateVersion: '1.0.0',
+ },
+ lifeCycles: {
+ constructor: {
+ type: 'js',
+ compiled:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ source:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ },
+ },
+ condition: true,
+ css:
+ 'body{background-color:#f2f3f5}.card_kgaqfbm5 {\n margin-bottom: 12px;\n}.card_kgaqfbm6 {\n margin-bottom: 12px;\n}.button_kgaqfbm7 {\n margin-right: 16px;\n width: 80px\n}.button_kgaqfbm8 {\n width: 80px;\n}.div_kgaqfbm9 {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ methods: {
+ __initMethods__: {
+ type: 'js',
+ source: 'function (exports, module) { /*set actions code here*/ }',
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
+ },
+ },
+ children: [
+ {
+ componentName: 'Div',
+ id: 'div',
+ props: {
+ className: 'div_kgaqfbm9',
+ behavior: 'NORMAL',
+ __style__:
+ ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ events: {},
+ fieldId: 'div_k1ow3h1o',
+ useFieldIdAsDomId: false,
+ customClassName: {
+ type: 'JSExpression',
+ value: 'getFromSomewhere()'
+ },
+ customClassName2: {
+ type: 'JSExpression',
+ mock: { hi: 'mock' },
+ value: 'getFromSomewhere()'
+ },
+ },
+ extraPropA: 'haha',
+ },
+ {
+ componentName: 'Div',
+ id: 'div2',
+ props: {
+ className: 'div_kgaqfbm9',
+ behavior: 'NORMAL',
+ __style__:
+ ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ events: {},
+ fieldId: 'div_k1ow3h1o',
+ useFieldIdAsDomId: false,
+ customClassName: '',
+ },
+ extraPropA: 'haha',
+ },
+ {
+ componentName: 'Test',
+ id: 'test',
+ props: {
+ className: 'div_kgaqfbm9',
+ behavior: 'NORMAL',
+ __style__:
+ ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ events: {},
+ fieldId: 'div_k1ow3h1o',
+ useFieldIdAsDomId: false,
+ customClassName: '',
+ },
+ extraPropA: 'haha',
+ }
+ ],
+};
diff --git a/packages/designer/tests/fixtures/unhandled-rejection.ts b/packages/designer/tests/fixtures/unhandled-rejection.ts
new file mode 100644
index 000000000..d69ffa08b
--- /dev/null
+++ b/packages/designer/tests/fixtures/unhandled-rejection.ts
@@ -0,0 +1,7 @@
+if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
+ process.on('unhandledRejection', reason => {
+ throw reason;
+ })
+ // Avoid memory leak by adding too many listeners
+ process.env.LISTENING_TO_UNHANDLED_REJECTION = true;
+}
\ No newline at end of file
diff --git a/packages/designer/tests/fixtures/window.ts b/packages/designer/tests/fixtures/window.ts
new file mode 100644
index 000000000..d772d1ef4
--- /dev/null
+++ b/packages/designer/tests/fixtures/window.ts
@@ -0,0 +1,18 @@
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+});
+
+Object.defineProperty(window, 'React', {
+ writable: true,
+ value: {},
+});
\ No newline at end of file
diff --git a/packages/designer/tests/meta/component-meta.test.ts b/packages/designer/tests/meta/component-meta.test.ts
new file mode 100644
index 000000000..f3cdfd2a6
--- /dev/null
+++ b/packages/designer/tests/meta/component-meta.test.ts
@@ -0,0 +1,71 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import divMeta from '../fixtures/component-metadata/div';
+import { ComponentMeta, isComponentMeta, removeBuiltinComponentAction, addBuiltinComponentAction } from '../../src/component-meta';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getGlobalComponentActions: () => [],
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('组件元数据处理', () => {
+ it('构造函数', () => {
+ const meta = new ComponentMeta(designer, divMeta);
+ expect(meta.isContainer).toBeTruthy;
+ expect(isComponentMeta(meta)).toBeTruthy;
+ expect(meta.acceptable).toBeTruthy;
+ expect(meta.isRootComponent()).toBeTruthy;
+ expect(meta.isModal).toBeFalsy;
+ expect(meta.rootSelector).toBeUndefined;
+ expect(meta.liveTextEditing).toBeUndefined;
+ expect(meta.descriptor).toBeUndefined;
+ expect(meta.icon).toBeUndefined;
+ expect(meta.getMetadata().title).toBe('容器');
+ expect(meta.title).toEqual({ type: 'i18n', 'en-US': 'Div', 'zh-CN': '容器' });
+
+ meta.setNpm({ package: '@ali/vc-div', componentName: 'Div' });
+ expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' });
+
+ meta.setMetadata(divMeta);
+ });
+
+ it('availableActions', () => {
+ const meta = new ComponentMeta(designer, divMeta);
+ expect(meta.availableActions).toHaveLength(3);
+ expect(meta.availableActions[0].name).toBe('remove');
+ expect(meta.availableActions[1].name).toBe('hide');
+ expect(meta.availableActions[2].name).toBe('copy');
+
+ removeBuiltinComponentAction('remove');
+ // availableActions 有 computed 缓存
+ expect(meta.availableActions[0].name).toBe('remove');
+ expect(meta.availableActions[1].name).toBe('hide');
+ expect(meta.availableActions[2].name).toBe('copy');
+
+ addBuiltinComponentAction({
+ name: 'new',
+ content: {
+ action() {}
+ }
+ });
+ // availableActions 有 computed 缓存
+ expect(meta.availableActions).toHaveLength(3);
+ expect(meta.availableActions[0].name).toBe('remove');
+ expect(meta.availableActions[1].name).toBe('hide');
+ expect(meta.availableActions[2].name).toBe('copy');
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/node/node.add.test.ts b/packages/designer/tests/node/node.add.test.ts
new file mode 100644
index 000000000..9da7a3473
--- /dev/null
+++ b/packages/designer/tests/node/node.add.test.ts
@@ -0,0 +1,564 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+import { EBADF } from 'constants';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('schema 生成节点模型测试', () => {
+ describe('block ❌ | component ❌ | slot ❌', () => {
+ let project: Project;
+ beforeEach(() => {
+ project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ });
+ afterEach(() => {
+ project.unload();
+ });
+ it('基本的节点模型初始化,模型导出', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ ids.forEach(id => {
+ expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
+ });
+
+ const pageNode = currentDocument?.getNode('node_k1ow3cb9');
+ expect(pageNode?.getComponentName()).toBe('Page');
+ expect(pageNode?.getIcon()).toBeUndefined;
+
+ const exportSchema = currentDocument?.export(1);
+ expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
+ expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
+ });
+
+ it('基本的节点模型初始化,节点深度', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const getNode = currentDocument.getNode.bind(currentDocument);
+
+ const pageNode = getNode('node_k1ow3cb9');
+ const rootHeaderNode = getNode('node_k1ow3cba');
+ const rootContentNode = getNode('node_k1ow3cbb');
+ const rootFooterNode = getNode('node_k1ow3cbc');
+ const formNode = getNode('form');
+ const cardNode = getNode('node_k1ow3cbj');
+ const cardContentNode = getNode('node_k1ow3cbk');
+ const columnsLayoutNode = getNode('node_k1ow3cbw');
+ const columnNode = getNode('node_k1ow3cbx');
+ const textFieldNode = getNode('node_k1ow3cbz');
+
+ expect(pageNode?.zLevel).toBe(0);
+ expect(rootHeaderNode?.zLevel).toBe(1);
+ expect(rootContentNode?.zLevel).toBe(1);
+ expect(rootFooterNode?.zLevel).toBe(1);
+ expect(formNode?.zLevel).toBe(2);
+ expect(cardNode?.zLevel).toBe(3);
+ expect(cardContentNode?.zLevel).toBe(4);
+ expect(columnsLayoutNode?.zLevel).toBe(5);
+ expect(columnNode?.zLevel).toBe(6);
+ expect(textFieldNode?.zLevel).toBe(7);
+
+ expect(textFieldNode?.getZLevelTop(7)).toEqual(textFieldNode);
+ expect(textFieldNode?.getZLevelTop(6)).toEqual(columnNode);
+ expect(textFieldNode?.getZLevelTop(5)).toEqual(columnsLayoutNode);
+ expect(textFieldNode?.getZLevelTop(4)).toEqual(cardContentNode);
+ expect(textFieldNode?.getZLevelTop(3)).toEqual(cardNode);
+ expect(textFieldNode?.getZLevelTop(2)).toEqual(formNode);
+ expect(textFieldNode?.getZLevelTop(1)).toEqual(rootContentNode);
+ expect(textFieldNode?.getZLevelTop(0)).toEqual(pageNode);
+
+ // 异常情况
+ expect(textFieldNode?.getZLevelTop(8)).toBeNull;
+ expect(textFieldNode?.getZLevelTop(-1)).toBeNull;
+ });
+
+ it('基本的节点模型初始化,节点父子、兄弟相关方法', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const getNode = currentDocument.getNode.bind(currentDocument);
+
+ const pageNode = getNode('node_k1ow3cb9');
+ const rootHeaderNode = getNode('node_k1ow3cba');
+ const rootContentNode = getNode('node_k1ow3cbb');
+ const rootFooterNode = getNode('node_k1ow3cbc');
+ const formNode = getNode('form');
+ const cardNode = getNode('node_k1ow3cbj');
+ const cardContentNode = getNode('node_k1ow3cbk');
+ const columnsLayoutNode = getNode('node_k1ow3cbw');
+ const columnNode = getNode('node_k1ow3cbx');
+ const textFieldNode = getNode('node_k1ow3cbz');
+
+ expect(pageNode?.index).toBe(-1);
+ expect(pageNode?.children.toString()).toBe('[object Array]');
+ expect(pageNode?.children?.get(1)).toBe(rootContentNode);
+ expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode);
+ expect(pageNode?.getNode()).toBe(pageNode);
+
+ expect(rootFooterNode?.index).toBe(2);
+
+ expect(textFieldNode?.getParent()).toBe(columnNode);
+ expect(columnNode?.getParent()).toBe(columnsLayoutNode);
+ expect(columnsLayoutNode?.getParent()).toBe(cardContentNode);
+ expect(cardContentNode?.getParent()).toBe(cardNode);
+ expect(cardNode?.getParent()).toBe(formNode);
+ expect(formNode?.getParent()).toBe(rootContentNode);
+ expect(rootContentNode?.getParent()).toBe(pageNode);
+ expect(rootContentNode?.prevSibling).toBe(rootHeaderNode);
+ expect(rootContentNode?.nextSibling).toBe(rootFooterNode);
+
+ expect(pageNode?.isRoot()).toBe(true);
+ expect(pageNode?.contains(textFieldNode)).toBe(true);
+ expect(textFieldNode?.getRoot()).toBe(pageNode);
+ expect(columnNode?.getRoot()).toBe(pageNode);
+ expect(columnsLayoutNode?.getRoot()).toBe(pageNode);
+ expect(cardContentNode?.getRoot()).toBe(pageNode);
+ expect(cardNode?.getRoot()).toBe(pageNode);
+ expect(formNode?.getRoot()).toBe(pageNode);
+ expect(rootContentNode?.getRoot()).toBe(pageNode);
+ });
+
+ it('基本的节点模型初始化,节点新建、删除等事件', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const getNode = currentDocument.getNode.bind(currentDocument);
+ const createNode = currentDocument.createNode.bind(currentDocument);
+
+ const pageNode = getNode('node_k1ow3cb9');
+ const nodeCreateHandler = jest.fn();
+ currentDocument?.onNodeCreate(nodeCreateHandler);
+
+ const node = createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'haha',
+ }
+ });
+ currentDocument?.insertNode(pageNode, node);
+
+ expect(nodeCreateHandler).toHaveBeenCalledTimes(1);
+ expect(nodeCreateHandler.mock.calls[0][0]).toBe(node);
+ expect(nodeCreateHandler.mock.calls[0][0].componentName).toBe('TextInput');
+ expect(nodeCreateHandler.mock.calls[0][0].getPropValue('propA')).toBe('haha');
+
+ const nodeDestroyHandler = jest.fn();
+ currentDocument?.onNodeDestroy(nodeDestroyHandler);
+ node.remove();
+ expect(nodeDestroyHandler).toHaveBeenCalledTimes(1);
+ expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node);
+ expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput');
+ expect(nodeDestroyHandler.mock.calls[0][0].getPropValue('propA')).toBe('haha');
+ });
+
+ it.skip('基本的节点模型初始化,节点插入等方法', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const getNode = currentDocument.getNode.bind(currentDocument);
+
+ const formNode = getNode('form');
+ const node1 = currentDocument.createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'haha',
+ }
+ });
+ const node2 = currentDocument.createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'heihei',
+ }
+ });
+ const node3 = currentDocument.createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'heihei2',
+ }
+ });
+ const node4 = currentDocument.createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'heihei3',
+ }
+ });
+
+ formNode?.insertBefore(node2);
+ // formNode?.insertBefore(node1, node2);
+ // formNode?.insertAfter(node3);
+ // formNode?.insertAfter(node4, node3);
+
+ expect(formNode?.children?.get(0)).toBe(node1);
+ expect(formNode?.children?.get(1)).toBe(node2);
+ // expect(formNode?.children?.get(5)).toBe(node3);
+ // expect(formNode?.children?.get(6)).toBe(node4);
+ });
+
+ it('基本的节点模型初始化,节点其他方法', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const getNode = currentDocument.getNode.bind(currentDocument);
+
+ const pageNode = getNode('node_k1ow3cb9');
+ expect(pageNode?.isPage()).toBe(true);
+ expect(pageNode?.isComponent()).toBe(false);
+ expect(pageNode?.isSlot()).toBe(false);
+ expect(pageNode?.title).toBe('hey, i\' a page!');
+ });
+
+ describe('节点新增(insertNode)', () => {
+ let project: Project;
+ beforeEach(() => {
+ project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ });
+ it('场景一:插入 NodeSchema,不指定 index', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ const formNode2 = currentDocument?.getNode('form');
+ expect(formNode).toEqual(formNode2);
+ currentDocument?.insertNode(formNode, {
+ componentName: 'TextInput',
+ id: 'nodeschema-id1',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ });
+ expect(nodesMap.size).toBe(ids.length + 1);
+ expect(formNode.children?.length).toBe(4);
+ const insertedNode = formNode.children.get(formNode.children.length - 1);
+ expect(insertedNode.componentName).toBe('TextInput');
+ expect(insertedNode.propsData).toEqual({
+ propA: 'haha',
+ propB: 3
+ });
+ // TODO: 把 checkId 的 commit pick 过来
+ // expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
+ });
+
+ it('场景一:插入 NodeSchema,指定 index: 0', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const nodesMap = currentDocument.nodesMap;
+ const formNode = nodesMap.get('form');
+ currentDocument?.insertNode(formNode, {
+ componentName: 'TextInput',
+ id: 'nodeschema-id1',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ }, 0);
+ expect(nodesMap.size).toBe(ids.length + 1);
+ expect(formNode.children.length).toBe(4);
+ const insertedNode = formNode.children.get(0);
+ expect(insertedNode.componentName).toBe('TextInput');
+ expect(insertedNode.propsData).toEqual({
+ propA: 'haha',
+ propB: 3
+ });
+ // TODO: 把 checkId 的 commit pick 过来
+ // expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
+ });
+
+ it('场景一:插入 NodeSchema,指定 index: 1', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form');
+ currentDocument?.insertNode(formNode, {
+ componentName: 'TextInput',
+ id: 'nodeschema-id1',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ }, 1);
+ expect(nodesMap.size).toBe(ids.length + 1);
+ expect(formNode.children.length).toBe(4);
+ const insertedNode = formNode.children.get(1);
+ expect(insertedNode.componentName).toBe('TextInput');
+ expect(insertedNode.propsData).toEqual({
+ propA: 'haha',
+ propB: 3
+ });
+ // TODO: 把 checkId 的 commit pick 过来
+ // expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
+ });
+
+ it('场景一:插入 NodeSchema,有 children', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ currentDocument?.insertNode(formNode, {
+ componentName: 'ParentNode',
+ props: {
+ propA: 'haha',
+ propB: 3
+ },
+ children: [
+ {
+ componentName: 'SubNode',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ },
+ {
+ componentName: 'SubNode2',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ }
+ ]
+ });
+ expect(nodesMap.size).toBe(ids.length + 3);
+ expect(formNode.children.length).toBe(4);
+ expect(formNode.children?.get(3)?.componentName).toBe('ParentNode');
+ expect(formNode.children?.get(3)?.children?.get(0)?.componentName).toBe('SubNode');
+ expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2');
+ });
+
+ it.skip('场景一:插入 NodeSchema,id 与现有 schema 里的 id 重复', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form');
+ currentDocument?.insertNode(formNode, {
+ componentName: 'TextInput',
+ id: 'nodeschema-id1',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ });
+ expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
+ expect(nodesMap.size).toBe(ids.length + 1);
+ });
+
+ it.skip('场景一:插入 NodeSchema,id 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form');
+ currentDocument?.insertNode(formNode, {
+ componentName: 'TextInput',
+ id: 'nodeschema-id1',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ });
+ expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
+ expect(nodesMap.size).toBe(ids.length + 1);
+ });
+
+ it('场景二:插入 Node 实例', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form');
+ const inputNode = currentDocument?.createNode({
+ componentName: 'TextInput',
+ id: 'nodeschema-id2',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ });
+ currentDocument?.insertNode(formNode, inputNode);
+ expect(formNode.children?.get(3)?.componentName).toBe('TextInput');
+ expect(nodesMap.size).toBe(ids.length + 1);
+ });
+
+ it('场景三:插入 JSExpression', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ currentDocument?.insertNode(formNode, {
+ type: 'JSExpression',
+ value: 'just a expression'
+ });
+ expect(nodesMap.size).toBe(ids.length + 1);
+ expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
+ // expect(formNode.children?.get(3)?.children).toEqual({
+ // type: 'JSExpression',
+ // value: 'just a expression'
+ // });
+ });
+ it('场景四:插入 string', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ currentDocument?.insertNode(formNode, 'just a string');
+ expect(nodesMap.size).toBe(ids.length + 1);
+ expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
+ // expect(formNode.children?.get(3)?.children).toBe('just a string');
+ });
+ });
+
+ describe('节点新增(insertNodes)', () => {
+ let project: Project;
+ beforeEach(() => {
+ project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ });
+ it('场景一:插入 NodeSchema,指定 index', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ const formNode2 = currentDocument?.getNode('form');
+ expect(formNode).toEqual(formNode2);
+ currentDocument?.insertNodes(formNode, [
+ {
+ componentName: 'TextInput',
+ props: {
+ propA: 'haha2',
+ propB: 3
+ }
+ },
+ {
+ componentName: 'TextInput2',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ }
+ ], 1);
+ expect(nodesMap.size).toBe(ids.length + 2);
+ expect(formNode.children?.length).toBe(5);
+ const insertedNode1 = formNode.children.get(1);
+ const insertedNode2 = formNode.children.get(2);
+ expect(insertedNode1.componentName).toBe('TextInput');
+ expect(insertedNode1.propsData).toEqual({
+ propA: 'haha2',
+ propB: 3
+ });
+ expect(insertedNode2.componentName).toBe('TextInput2');
+ expect(insertedNode2.propsData).toEqual({
+ propA: 'haha',
+ propB: 3
+ });
+ });
+
+ it('场景二:插入 Node 实例,指定 index', () => {
+ expect(project).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const formNode = nodesMap.get('form') as Node;
+ const formNode2 = currentDocument?.getNode('form');
+ expect(formNode).toEqual(formNode2);
+ const createdNode1 = currentDocument?.createNode({
+ componentName: 'TextInput',
+ props: {
+ propA: 'haha2',
+ propB: 3
+ }
+ });
+ const createdNode2 = currentDocument?.createNode({
+ componentName: 'TextInput2',
+ props: {
+ propA: 'haha',
+ propB: 3
+ }
+ });
+ currentDocument?.insertNodes(formNode, [ createdNode1, createdNode2 ], 1);
+ expect(nodesMap.size).toBe(ids.length + 2);
+ expect(formNode.children?.length).toBe(5);
+ const insertedNode1 = formNode.children.get(1);
+ const insertedNode2 = formNode.children.get(2);
+ expect(insertedNode1.componentName).toBe('TextInput');
+ expect(insertedNode1.propsData).toEqual({
+ propA: 'haha2',
+ propB: 3
+ });
+ expect(insertedNode2.componentName).toBe('TextInput2');
+ expect(insertedNode2.propsData).toEqual({
+ propA: 'haha',
+ propB: 3
+ });
+ });
+ });
+ })
+
+ describe('block ❌ | component ❌ | slot ✅', () => {
+ it('基本的 slot 创建', () => {
+ const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title.type', 'JSSlot');
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchemaWithSlot,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ // 目前每个 slot 会新增(1 + children.length)个节点
+ const expectedNodeCnt = ids.length + 2;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ // PageHeader
+ expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1);
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/node/node.dragdrop.test.ts b/packages/designer/tests/node/node.dragdrop.test.ts
new file mode 100644
index 000000000..be15bdfa7
--- /dev/null
+++ b/packages/designer/tests/node/node.dragdrop.test.ts
@@ -0,0 +1,61 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe.skip('节点拖拽测试', () => {
+ describe('block ❌ | component ❌ | slot ❌', () => {
+ it('修改普通属性,string | number', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ ids.forEach(id => {
+ expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
+ });
+
+ const exportSchema = currentDocument?.export(1);
+ expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
+ expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
+ });
+
+
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/node/node.modify.test.ts b/packages/designer/tests/node/node.modify.test.ts
new file mode 100644
index 000000000..e064d273f
--- /dev/null
+++ b/packages/designer/tests/node/node.modify.test.ts
@@ -0,0 +1,456 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('schema 生成节点模型测试', () => {
+ describe('block ❌ | component ❌ | slot ❌', () => {
+ let project: Project;
+ beforeEach(() => {
+ project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ });
+ it('读取普通属性,string | number | object', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+ /*
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ obj: {
+ a: 1,
+ b: false,
+ c: 'string',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ },
+ id: 'form',
+ condition: true,
+ */
+ const sizeProp = formNode?.getProp('size');
+ const sizeProp2 = formNode?.getProps().getProp('size');
+ expect(sizeProp).toBe(sizeProp2);
+ expect(sizeProp?.getAsString()).toBe('medium');
+ expect(sizeProp?.getValue()).toBe('medium');
+
+ const autoValidateProp = formNode?.getProp('autoValidate');
+ expect(autoValidateProp?.getValue()).toBe(true);
+
+ const objProp = formNode?.getProp('obj');
+ expect(objProp?.getValue()).toEqual({
+ a: 1,
+ b: false,
+ c: 'string',
+ });
+ const objAProp = formNode?.getProp('obj.a');
+ const objBProp = formNode?.getProp('obj.b');
+ const objCProp = formNode?.getProp('obj.c');
+ expect(objAProp?.getValue()).toBe(1);
+ expect(objBProp?.getValue()).toBe(false);
+ expect(objCProp?.getValue()).toBe('string');
+
+ const idProp = formNode?.getExtraProp('extraPropA');
+ expect(idProp?.getValue()).toBe('extraPropA');
+ });
+
+ it('修改普通属性,string | number | object,使用 Node 实例接口', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+ /*
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ obj: {
+ a: 1,
+ b: false,
+ c: 'string',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ },
+ id: 'form',
+ condition: true,
+ */
+ formNode?.setPropValue('size', 'large');
+ const sizeProp = formNode?.getProp('size');
+ expect(sizeProp?.getAsString()).toBe('large');
+ expect(sizeProp?.getValue()).toBe('large');
+
+ formNode?.setPropValue('autoValidate', false);
+ const autoValidateProp = formNode?.getProp('autoValidate');
+ expect(autoValidateProp?.getValue()).toBe(false);
+
+ formNode?.setPropValue('obj', {
+ a: 2,
+ b: true,
+ c: 'another string'
+ });
+ const objProp = formNode?.getProp('obj');
+ expect(objProp?.getValue()).toEqual({
+ a: 2,
+ b: true,
+ c: 'another string',
+ });
+ formNode?.setPropValue('obj.a', 3);
+ formNode?.setPropValue('obj.b', false);
+ formNode?.setPropValue('obj.c', 'string');
+ const objAProp = formNode?.getProp('obj.a');
+ const objBProp = formNode?.getProp('obj.b');
+ const objCProp = formNode?.getProp('obj.c');
+ expect(objAProp?.getValue()).toBe(3);
+ expect(objBProp?.getValue()).toBe(false);
+ expect(objCProp?.getValue()).toBe('string');
+ expect(objProp?.getValue()).toEqual({
+ a: 3,
+ b: false,
+ c: 'string',
+ });
+ });
+
+ it('修改普通属性,string | number | object,使用 Props 实例接口', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+ /*
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ obj: {
+ a: 1,
+ b: false,
+ c: 'string',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ },
+ id: 'form',
+ condition: true,
+ */
+ const props = formNode?.getProps();
+ props?.setPropValue('size', 'large');
+ const sizeProp = formNode?.getProp('size');
+ expect(sizeProp?.getAsString()).toBe('large');
+ expect(sizeProp?.getValue()).toBe('large');
+
+ props?.setPropValue('autoValidate', false);
+ const autoValidateProp = formNode?.getProp('autoValidate');
+ expect(autoValidateProp?.getValue()).toBe(false);
+
+ props?.setPropValue('obj', {
+ a: 2,
+ b: true,
+ c: 'another string'
+ });
+ const objProp = formNode?.getProp('obj');
+ expect(objProp?.getValue()).toEqual({
+ a: 2,
+ b: true,
+ c: 'another string',
+ });
+ props?.setPropValue('obj.a', 3);
+ props?.setPropValue('obj.b', false);
+ props?.setPropValue('obj.c', 'string');
+ const objAProp = formNode?.getProp('obj.a');
+ const objBProp = formNode?.getProp('obj.b');
+ const objCProp = formNode?.getProp('obj.c');
+ expect(objAProp?.getValue()).toBe(3);
+ expect(objBProp?.getValue()).toBe(false);
+ expect(objCProp?.getValue()).toBe('string');
+ expect(objProp?.getValue()).toEqual({
+ a: 3,
+ b: false,
+ c: 'string',
+ });
+ });
+
+ it('修改普通属性,string | number | object,使用 Prop 实例接口', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+ /*
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ obj: {
+ a: 1,
+ b: false,
+ c: 'string',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ },
+ id: 'form',
+ condition: true,
+ */
+ const sizeProp = formNode?.getProp('size');
+ sizeProp?.setValue('large');
+ expect(sizeProp?.getAsString()).toBe('large');
+ expect(sizeProp?.getValue()).toBe('large');
+
+ const autoValidateProp = formNode?.getProp('autoValidate');
+ autoValidateProp?.setValue(false);
+ expect(autoValidateProp?.getValue()).toBe(false);
+
+
+ const objProp = formNode?.getProp('obj');
+ objProp?.setValue({
+ a: 2,
+ b: true,
+ c: 'another string'
+ });
+ expect(objProp?.getValue()).toEqual({
+ a: 2,
+ b: true,
+ c: 'another string',
+ });
+ const objAProp = formNode?.getProp('obj.a');
+ const objBProp = formNode?.getProp('obj.b');
+ const objCProp = formNode?.getProp('obj.c');
+ objAProp?.setValue(3);
+ objBProp?.setValue(false);
+ objCProp?.setValue('string');
+ expect(objAProp?.getValue()).toBe(3);
+ expect(objBProp?.getValue()).toBe(false);
+ expect(objCProp?.getValue()).toBe('string');
+ expect(objProp?.getValue()).toEqual({
+ a: 3,
+ b: false,
+ c: 'string',
+ });
+ });
+ });
+
+ describe('block ❌ | component ❌ | slot ✅', () => {
+ let project: Project;
+ beforeEach(() => {
+ project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ });
+ it('修改 slot 属性,初始存在 slot 属性名,正常生成节点模型', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+
+ formNode?.setPropValue('slotA', {
+ type: 'JSSlot',
+ value: [{
+ componentName: 'TextInput1',
+ props: {
+ txt: 'haha',
+ num: 1,
+ bool: true
+ }
+ }, {
+ componentName: 'TextInput2',
+ props: {
+ txt: 'heihei',
+ num: 2,
+ bool: false
+ }
+ }]
+ });
+
+ expect(nodesMap.size).toBe(ids.length + 3);
+ expect(formNode?.slots).toHaveLength(1);
+ expect(formNode?.slots[0].children).toHaveLength(2);
+ const firstChildNode = formNode?.slots[0].children?.get(0);
+ const secondChildNode = formNode?.slots[0].children?.get(1);
+ expect(firstChildNode?.componentName).toBe('TextInput1');
+ expect(firstChildNode?.getPropValue('txt')).toBe('haha');
+ expect(firstChildNode?.getPropValue('num')).toBe(1);
+ expect(firstChildNode?.getPropValue('bool')).toBe(true);
+ expect(secondChildNode?.componentName).toBe('TextInput2');
+ expect(secondChildNode?.getPropValue('txt')).toBe('heihei');
+ expect(secondChildNode?.getPropValue('num')).toBe(2);
+ expect(secondChildNode?.getPropValue('bool')).toBe(false);
+ });
+
+ it('修改 slot 属性,初始存在 slot 属性名,关闭 slot', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+
+ formNode?.setPropValue('slotA', {
+ type: 'JSSlot',
+ value: [{
+ componentName: 'TextInput1',
+ props: {
+ txt: 'haha',
+ num: 1,
+ bool: true
+ }
+ }, {
+ componentName: 'TextInput2',
+ props: {
+ txt: 'heihei',
+ num: 2,
+ bool: false
+ }
+ }]
+ });
+
+ expect(nodesMap.size).toBe(ids.length + 3);
+ expect(formNode?.slots).toHaveLength(1);
+
+ formNode?.setPropValue('slotA', '');
+
+ expect(nodesMap.size).toBe(ids.length);
+ expect(formNode?.slots).toHaveLength(0);
+ });
+
+ it('修改 slot 属性,初始存在 slot 属性名,同名覆盖 slot', () => {
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const formNode = currentDocument?.getNode('form');
+
+ formNode?.setPropValue('slotA', {
+ type: 'JSSlot',
+ name: 'slotA',
+ value: [{
+ componentName: 'TextInput1',
+ props: {
+ txt: 'haha',
+ num: 1,
+ bool: true
+ }
+ }, {
+ componentName: 'TextInput2',
+ props: {
+ txt: 'heihei',
+ num: 2,
+ bool: false
+ }
+ }]
+ });
+
+ expect(nodesMap.size).toBe(ids.length + 3);
+ expect(formNode?.slots).toHaveLength(1);
+ expect(formNode?.slots[0].children).toHaveLength(2);
+
+ let firstChildNode = formNode?.slots[0].children?.get(0);
+ expect(firstChildNode?.componentName).toBe('TextInput1');
+ expect(firstChildNode?.getPropValue('txt')).toBe('haha');
+ expect(firstChildNode?.getPropValue('num')).toBe(1);
+ expect(firstChildNode?.getPropValue('bool')).toBe(true);
+
+ formNode?.setPropValue('slotA', {
+ type: 'JSSlot',
+ name: 'slotA',
+ value: [{
+ componentName: 'TextInput3',
+ props: {
+ txt: 'xixi',
+ num: 3,
+ bool: false
+ }
+ }]
+ });
+
+ expect(nodesMap.size).toBe(ids.length + 2);
+ expect(formNode?.slots).toHaveLength(1);
+ expect(formNode?.slots[0].children).toHaveLength(1);
+ firstChildNode = formNode?.slots[0].children?.get(0);
+ expect(firstChildNode?.componentName).toBe('TextInput3');
+ expect(firstChildNode?.getPropValue('txt')).toBe('xixi');
+ expect(firstChildNode?.getPropValue('num')).toBe(3);
+ expect(firstChildNode?.getPropValue('bool')).toBe(false);
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/node/node.remove.test.ts b/packages/designer/tests/node/node.remove.test.ts
new file mode 100644
index 000000000..f68d57ca1
--- /dev/null
+++ b/packages/designer/tests/node/node.remove.test.ts
@@ -0,0 +1,122 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/cloneDeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('节点模型删除测试', () => {
+ it('删除叶子节点', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const originalNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(originalNodeCnt);
+
+ currentDocument?.removeNode('node_k1ow3cbn');
+ // Button#1
+ expect(nodesMap.size).toBe(originalNodeCnt - 1);
+
+ currentDocument?.removeNode(nodesMap.get('node_k1ow3cbp'));
+ // Button#2
+ expect(nodesMap.size).toBe(originalNodeCnt - 2);
+
+ currentDocument?.removeNode('unexisting_node');
+ expect(nodesMap.size).toBe(originalNodeCnt - 2);
+ });
+
+ it('删除叶子节点,带有 slot', () => {
+ const formSchemaWithSlot = set(cloneDeep(formSchema),
+ 'children[1].children[0].children[2].children[1].props.greeting.type', 'JSSlot');
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchemaWithSlot,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const originalNodeCnt = ids.length + 2;
+ expect(nodesMap.size).toBe(originalNodeCnt);
+
+ currentDocument?.removeNode('node_k1ow3cbp');
+ // Button + Slot + Text
+ expect(nodesMap.size).toBe(originalNodeCnt - 3);
+ });
+
+ it('删除分支节点', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const originalNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(originalNodeCnt);
+
+ currentDocument?.removeNode('node_k1ow3cbo');
+ // Div + 2 * Button
+ expect(nodesMap.size).toBe(originalNodeCnt - 3);
+ });
+
+ it('删除分支节点,带有 slot', () => {
+ const formSchemaWithSlot = set(cloneDeep(formSchema),
+ 'children[1].children[0].children[2].children[1].props.greeting.type', 'JSSlot');
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchemaWithSlot,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const originalNodeCnt = ids.length + 2;
+ expect(nodesMap.size).toBe(originalNodeCnt);
+
+ currentDocument?.removeNode('node_k1ow3cbo');
+ // Div + 2 * Button + Slot + Text
+ expect(nodesMap.size).toBe(originalNodeCnt - 5);
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/project/project.test.ts b/packages/designer/tests/project/project.test.ts
new file mode 100644
index 000000000..31ece4526
--- /dev/null
+++ b/packages/designer/tests/project/project.test.ts
@@ -0,0 +1,134 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import { Project } from '../../src/project/project';
+import { Node } from '../../src/document/node/node';
+import { Designer } from '../../src/designer/designer';
+import formSchema from '../fixtures/schema/form';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+const mockCreateSettingEntry = jest.fn();
+jest.mock('../../src/designer/designer', () => {
+ return {
+ Designer: jest.fn().mockImplementation(() => {
+ return {
+ getComponentMeta() {
+ return {
+ getMetadata() {
+ return { experimental: null };
+ },
+ };
+ },
+ transformProps(props) { return props; },
+ createSettingEntry: mockCreateSettingEntry,
+ postEvent() {},
+ };
+ }),
+ };
+});
+
+let designer = null;
+beforeAll(() => {
+ designer = new Designer({});
+});
+
+describe('schema 生成节点模型测试', () => {
+ describe('block ❌ | component ❌ | slot ❌', () => {
+ beforeEach(() => {
+ mockCreateSettingEntry.mockClear();
+ });
+
+ it('基本的节点模型初始化,模型导出,初始化传入 schema', () => {
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchema,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ ids.forEach(id => {
+ expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
+ });
+
+ const exportSchema = currentDocument?.export(1);
+ expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
+ expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
+ });
+
+ it('基本的节点模型初始化,模型导出,project.open 传入 schema', () => {
+ const project = new Project(designer);
+ project.open(formSchema);
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ ids.forEach(id => {
+ expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
+ });
+
+ const exportSchema = currentDocument?.export(1);
+ expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
+ expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
+ });
+
+ it('project 卸载所有 document - unload()', () => {
+ const project = new Project(designer);
+ project.open(formSchema);
+ expect(project).toBeTruthy();
+ const { currentDocument, documents } = project;
+
+ expect(documents).toHaveLength(1);
+ expect(currentDocument).toBe(documents[0]);
+
+ project.unload();
+
+ expect(documents).toHaveLength(0);
+ });
+
+ it('project 卸载指定 document - removeDocument()', () => {
+ const project = new Project(designer);
+ project.open(formSchema);
+ expect(project).toBeTruthy();
+ const { currentDocument, documents } = project;
+
+ expect(documents).toHaveLength(1);
+ expect(currentDocument).toBe(documents[0]);
+
+ project.removeDocument(currentDocument);
+
+ expect(documents).toHaveLength(0);
+ });
+ });
+
+ describe('block ❌ | component ❌ | slot ✅', () => {
+ it('基本的节点模型初始化,模型导出,初始化传入 schema', () => {
+ const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title.type', 'JSSlot');
+ const project = new Project(designer, {
+ componentsTree: [
+ formSchemaWithSlot,
+ ],
+ });
+ project.open();
+ expect(project).toBeTruthy();
+ const { currentDocument } = project;
+ const { nodesMap } = currentDocument!;
+ const ids = getIdsFromSchema(formSchema);
+ // 目前每个 slot 会新增(1 + children.length)个节点
+ const expectedNodeCnt = ids.length + 2;
+ expect(nodesMap.size).toBe(expectedNodeCnt);
+ // PageHeader
+ expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1);
+ });
+ });
+
+ describe.skip('多 document 测试', () => {
+
+ });
+});
\ No newline at end of file
diff --git a/packages/designer/tests/utils/bom.ts b/packages/designer/tests/utils/bom.ts
new file mode 100644
index 000000000..fca5f73fc
--- /dev/null
+++ b/packages/designer/tests/utils/bom.ts
@@ -0,0 +1,77 @@
+import { getMockRenderer } from './renderer';
+
+interface MockDocument extends Document {
+ // open(): any;
+ // write(): any;
+ // close(): any;
+ // addEventListener(): any;
+ // removeEventListener(): any;
+ triggerEventListener(): any;
+ // createElement(): any;
+ // appendChild(): any;
+ // removeChild(): any;
+}
+
+
+const eventsMap : Map> = new Map>();
+const mockAddEventListener = jest.fn((eventName: string, cb) => {
+ if (!eventsMap.has(eventName)) {
+ eventsMap.set(eventName, new Set([cb]));
+ return;
+ }
+ eventsMap.get(eventName)!.add(cb);
+});
+
+const mockRemoveEventListener = jest.fn((eventName: string, cb) => {
+ if (!eventsMap.has(eventName)) return;
+ if (!cb) {
+ eventsMap.delete(eventName);
+ return;
+ }
+ eventsMap.get(eventName)?.delete(cb);
+});
+
+const mockTriggerEventListener = jest.fn((eventName: string, data: any, context: object = {}) => {
+ if (!eventsMap.has(eventName)) return;
+ for (const cb of eventsMap.get(eventName)) {
+ cb.call(context, data);
+ }
+});
+
+const mockCreateElement = jest.fn((tagName) => {
+ return {
+ style: {},
+ appendChild() {},
+ addEventListener: mockAddEventListener,
+ removeEventListener: mockRemoveEventListener,
+ triggerEventListener: mockTriggerEventListener,
+ }
+})
+
+export function getMockDocument(): MockDocument {
+ return {
+ open() {},
+ write() {},
+ close() {},
+ addEventListener: mockAddEventListener,
+ removeEventListener: mockRemoveEventListener,
+ triggerEventListener: mockTriggerEventListener,
+ createElement: mockCreateElement,
+ removeChild() {},
+ body: { appendChild() {}, removeChild() {} },
+ };
+}
+
+export function getMockWindow(doc?: MockDocument) {
+ return {
+ SimulatorRenderer: getMockRenderer(),
+ addEventListener: mockAddEventListener,
+ removeEventListener: mockRemoveEventListener,
+ triggerEventListener: mockTriggerEventListener,
+ document: doc || getMockDocument(),
+ };
+}
+
+export function clearEventsMap() {
+ eventsMap.clear();
+}
\ No newline at end of file
diff --git a/packages/designer/tests/utils/event.ts b/packages/designer/tests/utils/event.ts
new file mode 100644
index 000000000..59f7b016e
--- /dev/null
+++ b/packages/designer/tests/utils/event.ts
@@ -0,0 +1,8 @@
+export function getMockEvent(target, options) {
+ return {
+ target,
+ preventDefault() {},
+ stopPropagation() {},
+ ...options,
+ };
+}
\ No newline at end of file
diff --git a/packages/designer/tests/utils/index.ts b/packages/designer/tests/utils/index.ts
new file mode 100644
index 000000000..74eb13265
--- /dev/null
+++ b/packages/designer/tests/utils/index.ts
@@ -0,0 +1,4 @@
+export { getIdsFromSchema, getNodeFromSchemaById } from '@ali/lowcode-test-mate/es/utils';
+export { getMockDocument, getMockWindow } from './bom';
+export { getMockEvent } from './event';
+export { getMockRenderer } from './renderer';
\ No newline at end of file
diff --git a/packages/designer/tests/utils/renderer.ts b/packages/designer/tests/utils/renderer.ts
new file mode 100644
index 000000000..256a8c651
--- /dev/null
+++ b/packages/designer/tests/utils/renderer.ts
@@ -0,0 +1,8 @@
+export function getMockRenderer() {
+ return {
+ isSimulatorRenderer: true,
+ run() {
+ console.log('renderer run');
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/designer/tsconfig.json b/packages/designer/tsconfig.json
deleted file mode 100644
index 4a965ec62..000000000
--- a/packages/designer/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "lib"
- },
- "include": [
- "./src/"
- ]
-}
-
diff --git a/packages/editor-core/.eslintrc.js b/packages/editor-core/.eslintrc.js
index c4749b311..f8bcac9a2 100644
--- a/packages/editor-core/.eslintrc.js
+++ b/packages/editor-core/.eslintrc.js
@@ -1,5 +1,5 @@
module.exports = {
- extends: '../../.eslintrc.js',
+ extends: 'eslint-config-ali/typescript/react',
rules: {
'react/no-multi-comp': 1,
'no-unused-expressions': 1,
diff --git a/packages/editor-core/src/utils/monitor.ts b/packages/editor-core/src/utils/monitor.ts
index 66ec0f39b..80313a55b 100644
--- a/packages/editor-core/src/utils/monitor.ts
+++ b/packages/editor-core/src/utils/monitor.ts
@@ -1,4 +1,4 @@
-class Monitor {
+export class Monitor {
fn = (params: any) => {
const { AES } = window as any;
if (typeof AES.log === 'function') {
diff --git a/packages/editor-preset-vision/.eslintrc.js b/packages/editor-preset-vision/.eslintrc.js
index f3461d8f8..4c845e2de 100644
--- a/packages/editor-preset-vision/.eslintrc.js
+++ b/packages/editor-preset-vision/.eslintrc.js
@@ -1,8 +1,8 @@
module.exports = {
- extends: '../../.eslintrc.js',
+ extends: 'eslint-config-ali/typescript/react',
rules: {
'react/no-multi-comp': 1,
- 'no-unused-expressions': 1,
+ 'no-unused-expressions': 0,
'implicit-arrow-linebreak': 1,
'no-nested-ternary': 1,
'no-mixed-operators': 1,
@@ -16,5 +16,6 @@ module.exports = {
'react/no-deprecated': 1,
'no-useless-escape': 1,
'brace-style': 1,
+ '@typescript-eslint/member-ordering': 0,
}
}
\ No newline at end of file
diff --git a/packages/editor-preset-vision/CHANGELOG.md b/packages/editor-preset-vision/CHANGELOG.md
index 44be5911c..9d26c2744 100644
--- a/packages/editor-preset-vision/CHANGELOG.md
+++ b/packages/editor-preset-vision/CHANGELOG.md
@@ -83,14 +83,27 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
-
-## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@1.0.11...@ali/lowcode-editor-preset-vision@1.0.12) (2020-10-20)
+
+## [0.12.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-3) (2020-10-12)
+
+
+### Bug Fixes
+
+* 去掉 flags ([75fc3c6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/75fc3c6))
+* 处理 JSExpreesion 的 i18n 场景 ([9b87407](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9b87407))
+
+
+
+
+
+## [0.12.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-1...v0.12.1-2) (2020-09-23)
### Bug Fixes
* 合并分支 ([add2f23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/add2f23))
* fix bug ([113e409](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/113e409))
+* i18n 绑定变量后消失 ([0aafafe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0aafafe))
@@ -119,14 +132,111 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
-
-## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@1.0.8-0...@ali/lowcode-editor-preset-vision@1.0.8) (2020-09-28)
+
+## [1.0.9-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-0...v1.0.9-1) (2020-09-14)
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
+
+## 1.0.9-0 (2020-09-14)
+
+
+### Bug Fixes
+
+* fieldId 重复问题 ([e761b1a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e761b1a))
+* 🐛 eslint ([14803dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14803dd))
+* 🐛 eslint ([e3ca0bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e3ca0bd))
+* 🐛 use intl ([a22e66a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a22e66a))
+* 🐛 用 isI18nData 判断 meta title ([732bccf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/732bccf))
+* 🐛 解决点击组件时无法聚焦到点中的组件上的问题 ([852d882](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/852d882))
+* 🐛 逻辑简化 ([710f3ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/710f3ba))
+* add extraEnv ([9058ac8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9058ac8))
+* compatiable bug ([45574db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/45574db))
+* compatiable old VE api ([45af1c5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/45af1c5))
+* compatiableReducer 递归 ([e905928](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e905928))
+* createComponent 支持所有 schema ([7f946f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7f946f5))
+* currentPage.id 返回 formUuid ([775725d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/775725d))
+* fieldId 重复 ([5d64312](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d64312))
+* fieldId 重置bug ([31215da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31215da))
+* formUuid 可能不在 url 中 ([8657ab8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8657ab8))
+* i18n parser & setting ([dbdd9e4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dbdd9e4))
+* modify layout props ([9baba75](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9baba75))
+* patch prototype ([f20bfaa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f20bfaa))
+* render children ([487f257](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/487f257))
+* settingField items is empty when type is not 'group' ([582c41a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/582c41a))
+* slot 兼容问题 + loop key bug fix ([bc64017](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc64017))
+* style ([4694331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4694331))
+* Trunk add getSetter ([b6d64c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b6d64c3))
+* Trunk.getSetter return ReactElement ([34bf71d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34bf71d))
+* upgradePropsReducer ([e68977f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e68977f))
+* variable init bug ([6d55bd3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d55bd3))
+* vc-filter bug fix ([31ea5d5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31ea5d5))
+* vision API 兼容 DockPane.getDocks() ([f72fb66](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f72fb66))
+* vision prop 初始化时有依赖已初始化的 prop,需要实时添加 ([1feb46f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1feb46f))
+* vision 大包 window 指向问题 ([aa1b526](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa1b526))
+* 不对外暴露 Node ([05957ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05957ce))
+* 不应该限定 parent 才做解绑操作 ([2e616e3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e616e3))
+* 使用深拷贝赋值并修改 dataSource.list 避免影响 legao 现有逻辑 ([82c5d2e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/82c5d2e))
+* 保存区块按钮渲染异常 ([33a7227](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/33a7227))
+* 修复 initial 重复、type = 'composite' 时 items 为空 ([bf79e63](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bf79e63))
+* 修复 preset-vision 版本 lifeCycles 丢失以及 slot 初始化问题 ([7cf6d24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7cf6d24))
+* 修复 slot 获取初始值异常的 bug ([63b19f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/63b19f1))
+* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542))
+* 修复组件面板详情加载不了的 bug ([cca3309](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cca3309))
+* 修复获取 currentPage 的逻辑 ([d8221db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d8221db))
+* 兼容 listSetter 内部变量,修复回退 fieldId 重置问题 ([c95e618](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c95e618))
+* 兼容 rpx ([5050af7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5050af7))
+* 兼容 variable 历史数据格式 ([d666317](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d666317))
+* 兼容事件绑定 ([f4c07af](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f4c07af))
+* 兼容原来 prototype 的 componentName/view ([d542a40](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d542a40))
+* 区块组件无法删除 ([d936d2b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d936d2b))
+* 可以降级到历史的 JSBlock 格式 ([af1746b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af1746b))
+* 右侧配置面板样式修复 ([05f62da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05f62da))
+* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
+* 在 renderer 层面做 function component 包装,避免影响 rax 等其他场景 ([1f920dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f920dd))
+* 增加兼容 API ([2960446](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2960446))
+* 处理 function component 无法选中的问题,本质上是没有 ref ([fa94aab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fa94aab))
+* 支持 AC 组件 ([c287bad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c287bad))
+* 支持事件 VE_EVENTS.VE_PAGE_PAGE_READY ([935ffad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/935ffad))
+* **editor-skeleton:** add canSetFixed prop to panel config ([1b57d5c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b57d5c))
+* 支持页面回滚 ([5d7dc2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d7dc2f))
+* 框架样式调整 ([58790c5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/58790c5))
+* 用户在动态修改 prototype 时也需要重新计算 meta ([66c21c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66c21c0))
+* 移除 isInSimulator 函数 ([6370889](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6370889))
+* 简化 onPageReady 实现逻辑 ([a36e5f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a36e5f2))
+* 补全 packageName, 否则在组件面板会被隐藏 ([88e5008](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/88e5008))
+* 调整 upgrade 和 init 的流程 ([09fc1a0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/09fc1a0))
+* 调整保存成功弹出框位置 ([5198dae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5198dae))
+* 页面加载之后就被标记位 isModified ([2840d27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2840d27))
+
+
+### Features
+
+* 🎸 prototype getTitle 支持 i18n ([18807ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18807ab))
+* complete live-editing expr & i18n ([3ac08ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ac08ba))
+* export Monitor ([51025f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/51025f0))
+* get layout config from legao-design ([b9103a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b9103a2))
+* JSexpression props ([26f4fb1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26f4fb1))
+* live mode lifeCycles ([66f0c79](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66f0c79))
+* live 模式取消 mock 兼容 ([ab66fd4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ab66fd4))
+* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039))
+* register-defaults 改为可选项 ([2195797](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2195797))
+* support prop.autorun ([c0a5235](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c0a5235))
+* support subtreeModified ([7eeb51c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7eeb51c))
+* ve事件埋点 ([700e5b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/700e5b0))
+* 在 editor-preset-vision 中对 legao schema 进行向前兼容 ([7867917](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7867917))
+* 增加 defaultFixed,面板可默认固定 ([eb51b5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb51b5e))
+* 支持 entry 模式 ([fe1f6f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fe1f6f1))
+* 支持多 pages 的 schema 结构 ([d9b5adb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d9b5adb))
+* 适配 webtable ([91f1702](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91f1702))
+* 适配乐高 OneApi 数据源,将 options.params 从 Array 改为 Object ([aa135c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa135c0))
+
+
+
+
## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@0.8.57...@ali/lowcode-editor-preset-vision@1.0.8-0) (2020-09-09)
diff --git a/packages/editor-preset-vision/build.json b/packages/editor-preset-vision/build.json
index ce6117bb0..ffd529288 100644
--- a/packages/editor-preset-vision/build.json
+++ b/packages/editor-preset-vision/build.json
@@ -10,10 +10,11 @@
"react": "var window.React",
"react-dom": "var window.ReactDOM",
"prop-types": "var window.PropTypes",
- "rax": "var window.Rax",
+ "@ali/visualengine": "var window.VisualEngine",
"@ali/visualengine-utils": "var window.VisualEngineUtils",
- "monaco-editor/esm/vs/editor/editor.api":"var window.monaco",
- "monaco-editor/esm/vs/editor/editor.main.js":"var window.monaco"
+ "rax": "var window.Rax",
+ "monaco-editor/esm/vs/editor/editor.api": "var window.monaco",
+ "monaco-editor/esm/vs/editor/editor.main.js": "var window.monaco"
}
}
],
diff --git a/packages/editor-preset-vision/build.test.json b/packages/editor-preset-vision/build.test.json
new file mode 100644
index 000000000..4bde6acd1
--- /dev/null
+++ b/packages/editor-preset-vision/build.test.json
@@ -0,0 +1,19 @@
+{
+ "plugins": [
+ [
+ "build-plugin-component",
+ {
+ "filename": "editor-preset-vision",
+ "library": "LowcodeEditor",
+ "libraryTarget": "umd",
+ "externals": {
+ "react": "var window.React",
+ "react-dom": "var window.ReactDOM",
+ "prop-types": "var window.PropTypes",
+ "rax": "var window.Rax"
+ }
+ }
+ ],
+ "@ali/lowcode-test-mate/plugin/index.ts"
+ ]
+}
diff --git a/packages/editor-preset-vision/jest.config.js b/packages/editor-preset-vision/jest.config.js
new file mode 100644
index 000000000..31a6eab6e
--- /dev/null
+++ b/packages/editor-preset-vision/jest.config.js
@@ -0,0 +1,27 @@
+const esModules = [
+ '@recore/obx-react',
+ // '@ali/lowcode-editor-core',
+].join('|');
+
+module.exports = {
+ // transform: {
+ // '^.+\\.[jt]sx?$': 'babel-jest',
+ // // '^.+\\.(ts|tsx)$': 'ts-jest',
+ // // '^.+\\.(js|jsx)$': 'babel-jest',
+ // },
+ // testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
+ transformIgnorePatterns: [
+ `/node_modules/(?!${esModules})/`,
+ ],
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
+ collectCoverage: false,
+ collectCoverageFrom: [
+ 'src/**/*.{ts,tsx}',
+ '!src/**/*.d.ts',
+ '!src/base/**',
+ '!src/fields/**',
+ '!src/prop.ts',
+ '!**/node_modules/**',
+ '!**/vendor/**',
+ ],
+};
diff --git a/packages/editor-preset-vision/package.json b/packages/editor-preset-vision/package.json
index 732f96c9d..618cdf232 100644
--- a/packages/editor-preset-vision/package.json
+++ b/packages/editor-preset-vision/package.json
@@ -10,8 +10,10 @@
"lib"
],
"scripts": {
+ "start": "build-scripts start",
"build": "build-scripts build --skip-demo",
- "cloud-build": "build-scripts build --skip-demo"
+ "cloud-build": "build-scripts build --skip-demo",
+ "test": "build-scripts test --config build.test.json"
},
"license": "MIT",
"dependencies": {
@@ -20,6 +22,7 @@
"@ali/lowcode-editor-skeleton": "^1.0.22",
"@ali/lowcode-plugin-designer": "^1.0.22",
"@ali/lowcode-plugin-outline-pane": "^1.0.21",
+ "@ali/lowcode-utils": "^1.0.21",
"@ali/ve-i18n-util": "^2.0.0",
"@ali/ve-icons": "^4.1.9",
"@ali/ve-less-variables": "2.0.3",
@@ -37,6 +40,7 @@
"react-dom": "^16.8.1"
},
"devDependencies": {
+ "@ali/lowcode-test-mate": "^1.0.1",
"@alib/build-scripts": "^0.1.18",
"@types/domready": "^1.0.0",
"@types/events": "^3.0.0",
@@ -45,6 +49,7 @@
"build-plugin-fusion": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0",
"build-plugin-react-app": "^1.1.2",
+ "prop-types": "^15.7.2",
"tsconfig-paths-webpack-plugin": "^3.2.0"
},
"publishConfig": {
diff --git a/packages/editor-preset-vision/src/bundle/prototype.ts b/packages/editor-preset-vision/src/bundle/prototype.ts
index b91554240..eb93cb184 100644
--- a/packages/editor-preset-vision/src/bundle/prototype.ts
+++ b/packages/editor-preset-vision/src/bundle/prototype.ts
@@ -16,6 +16,7 @@ import {
upgradePropConfig,
upgradeConfigure,
} from './upgrade-metadata';
+import { accessLibrary } from '@ali/lowcode-utils';
import { designer } from '../editor';
@@ -103,8 +104,8 @@ registerMetadataTransducer(
let top: FieldConfig[];
let bottom: FieldConfig[];
if (combined) {
- top = combined?.[0].items || combined;
- bottom = combined?.[combined.length - 1].items || combined;
+ top = combined?.[0]?.items || combined;
+ bottom = combined?.[combined.length - 1]?.items || combined;
} else if (props) {
top = props;
bottom = props;
@@ -166,15 +167,6 @@ export interface OldGlobalPropConfig extends OldPropConfig {
const packageMaps: any = {};
-function accessLibrary(library: string | object) {
- if (typeof library !== 'string') {
- return library;
- }
-
- // TODO: enhance logic
- return (window as any)[library];
-}
-
export function setPackages(packages: Array<{ package: string; library: object | string }>) {
packages.forEach((item) => {
let lib: any;
@@ -236,6 +228,14 @@ class Prototype {
return this.meta.npm?.package;
}
+ set packageName(pkgName) {
+ if (this.meta.npm) {
+ this.meta.npm.package = pkgName;
+ } else {
+ this.meta.npm = { package: pkgName };
+ }
+ }
+
// 兼容原 vision 用法
view: ComponentType | undefined;
diff --git a/packages/editor-preset-vision/src/bundle/trunk.ts b/packages/editor-preset-vision/src/bundle/trunk.ts
index 5144275ad..c84475659 100644
--- a/packages/editor-preset-vision/src/bundle/trunk.ts
+++ b/packages/editor-preset-vision/src/bundle/trunk.ts
@@ -30,7 +30,7 @@ export class Trunk {
}
getList(): any[] {
- const list = this.trunk.reduceRight((prev, cur) => prev.concat(cur.getList()), []);
+ const list = this.trunk.filter(o => o).reduceRight((prev, cur) => prev.concat(cur.getList()), []);
const result: Prototype[] = [];
list.forEach((item: Prototype) => {
if (!result.find(r => r.options.componentName === item.options.componentName)) {
@@ -98,7 +98,6 @@ export class Trunk {
}
registerSetter(type: string, setter: CustomView | RegisteredSetter) {
- // console.warn('Trunk.registerSetter is deprecated');
registerSetter(type, setter);
}
diff --git a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts
index b0931d6ca..084037dfc 100644
--- a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts
+++ b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts
@@ -136,6 +136,7 @@ export interface OldPrototypeConfig {
};
isContainer?: boolean; // => configure.component.isContainer
+ isAbsoluteLayoutContainer?: boolean; // => meta.experimental.isAbsoluteLayoutContainer 是否是绝对定位容器
isModal?: boolean; // => configure.component.isModal
isFloating?: boolean; // => configure.component.isFloating
descriptor?: string; // => configure.component.descriptor
@@ -171,6 +172,7 @@ export interface OldPrototypeConfig {
onResizeEnd?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
devMode?: string;
schema?: ProjectSchema;
+ isTopFixed?: boolean;
}
export interface ISetterConfig {
@@ -592,6 +594,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
configure,
transducers,
isContainer,
+ isAbsoluteLayoutContainer,
rectSelector,
isModal,
isFloating,
@@ -620,6 +623,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
onResizeEnd, // onResizeEnd
devMode,
schema,
+ isTopFixed,
} = oldConfig;
const meta: any = {
@@ -678,7 +682,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
component.nestingRule = nestingRule;
// 未考虑清楚的,放在实验性段落
- const experimental: any = {};
+ const experimental: any = {
+ isAbsoluteLayoutContainer,
+ };
if (context) {
// for prototype.getContextInfo
experimental.context = context;
@@ -724,6 +730,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
if (view) {
experimental.view = view;
}
+ if (isTopFixed) {
+ experimental.isTopFixed = isTopFixed;
+ }
if (transducers) {
// Array<{ toStatic, toNative }>
// ? only twice
diff --git a/packages/editor-preset-vision/src/bus.ts b/packages/editor-preset-vision/src/bus.ts
index dd6b8386a..1a13ef263 100644
--- a/packages/editor-preset-vision/src/bus.ts
+++ b/packages/editor-preset-vision/src/bus.ts
@@ -64,16 +64,20 @@ export class Bus {
const bus = new Bus();
-editor.on('hotkey.callback.call', (data) => {
+editor?.on('hotkey.callback.call', (data) => {
bus.emit('ve.hotkey.callback.call', data);
});
-editor.on('history.back', (data) => {
+editor?.on('history.back', (data) => {
bus.emit('ve.history.back', data);
});
-editor.on('history.forward', (data) => {
+editor?.on('history.forward', (data) => {
bus.emit('ve.history.forward', data);
});
+editor?.on('node.prop.change', (data) => {
+ bus.emit('node.prop.change', data);
+});
+
export default bus;
diff --git a/packages/editor-preset-vision/src/deep-value-parser.ts b/packages/editor-preset-vision/src/deep-value-parser.ts
index e5270264a..5d15d9c0a 100644
--- a/packages/editor-preset-vision/src/deep-value-parser.ts
+++ b/packages/editor-preset-vision/src/deep-value-parser.ts
@@ -2,18 +2,27 @@ import Env from './env';
import { isJSSlot, isI18nData, isJSExpression } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils';
import i18nUtil from './i18n-util';
-
-function isVariable(obj: any) {
- return obj && obj.type === 'variable';
-}
+import { editor } from './editor';
+import { isVariable } from './utils';
// FIXME: 表达式使用 mock 值,未来live 模式直接使用原始值
+// TODO: designType
export function deepValueParser(obj?: any): any {
if (isJSExpression(obj)) {
+ if (editor.get('designMode') === 'live') {
+ return obj;
+ }
obj = obj.mock;
}
// 兼容 ListSetter 中的变量结构
if (isVariable(obj)) {
+ if (editor.get('designMode') === 'live') {
+ return {
+ type: 'JSExpression',
+ value: obj.variable,
+ mock: obj.value,
+ };
+ }
obj = obj.value;
}
if (!obj) {
diff --git a/packages/editor-preset-vision/src/editor.ts b/packages/editor-preset-vision/src/editor.ts
index 96ecf7229..9f17555c2 100644
--- a/packages/editor-preset-vision/src/editor.ts
+++ b/packages/editor-preset-vision/src/editor.ts
@@ -1,10 +1,8 @@
-import { isJSBlock, isJSExpression, isJSSlot, isI18nData } from '@ali/lowcode-types';
-import { isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
+import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
+import { isPlainObject, hasOwnProperty, cloneDeep, isI18NObject, isUseI18NSetter, convertToI18NObject, isString } from '@ali/lowcode-utils';
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, LiveEditing, TransformStage, Node, getConvertedExtraKey } from '@ali/lowcode-designer';
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
-import { toCss } from '@ali/vu-css-style';
-import logger from '@ali/vu-logger';
import bus from './bus';
import { VE_EVENTS } from './base/const';
@@ -13,6 +11,17 @@ import { Skeleton, SettingsPrimaryPane, registerDefaults } from '@ali/lowcode-ed
import { deepValueParser } from './deep-value-parser';
import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing';
+import {
+ compatibleReducer,
+ upgradePageLifeCyclesReducer,
+ stylePropsReducer,
+ upgradePropsReducer,
+ filterReducer,
+ removeEmptyPropsReducer,
+ initNodeReducer,
+ liveLifecycleReducer,
+ nodeTopFixedReducer,
+} from './props-reducers';
export const editor = new Editor();
globalContext.register(editor, Editor);
@@ -27,251 +36,39 @@ editor.set(Designer, designer);
editor.set('designer', designer);
designer.project.onCurrentDocumentChange((doc) => {
- doc.onRendererReady(() => {
- bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
- });
+ bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
+ editor.set('currentDocument', doc);
});
-interface Variable {
- type: 'variable';
- variable: string;
- value: any;
-}
-
-function isVariable(obj: any): obj is Variable {
- return obj && obj.type === 'variable';
-}
-
-function upgradePropsReducer(props: any) {
- if (!props || !isPlainObject(props)) {
- return props;
- }
- if (isJSBlock(props)) {
- if (props.value.componentName === 'Slot') {
- return {
- type: 'JSSlot',
- title: (props.value.props as any)?.slotTitle,
- name: (props.value.props as any)?.slotName,
- value: props.value.children,
- };
- } else {
- return props.value;
- }
- }
- if (isVariable(props)) {
- return {
- type: 'JSExpression',
- value: props.variable,
- mock: props.value,
- };
- }
- const newProps: any = {};
- Object.keys(props).forEach(key => {
- if (/^__slot__/.test(key) && props[key] === true) {
- return;
- }
- newProps[key] = upgradePropsReducer(props[key]);
- });
- return newProps;
-}
-
// 升级 Props
designer.addPropsReducer(upgradePropsReducer, TransformStage.Upgrade);
-function getCurrentFieldIds() {
- const fieldIds: any = [];
- const nodesMap = designer?.currentDocument?.nodesMap || new Map();
- nodesMap.forEach((curNode: any) => {
- const fieldId = nodesMap?.get(curNode.id)?.getPropValue('fieldId');
- if (fieldId) {
- fieldIds.push(fieldId);
- }
- });
- return fieldIds;
-}
-
// 节点 props 初始化
-designer.addPropsReducer((props, node) => {
- // run initials
- const newProps: any = {
- ...props,
- };
- if (newProps.fieldId) {
- const fieldIds = getCurrentFieldIds();
+designer.addPropsReducer(initNodeReducer, TransformStage.Init);
- // 全局的关闭 uniqueIdChecker 信号,在 ve-utils 中实现
- if (fieldIds.indexOf(props.fieldId) >= 0 && !(window as any).__disable_unique_id_checker__) {
- newProps.fieldId = undefined;
- }
- }
- const initials = node.componentMeta.getMetadata().experimental?.initials;
- if (initials) {
- const getRealValue = (propValue: any) => {
- if (isVariable(propValue)) {
- return propValue.value;
- }
- if (isJSExpression(propValue)) {
- return propValue.mock;
- }
- return propValue;
- };
- initials.forEach((item) => {
- // FIXME! this implements SettingTarget
- try {
- // FIXME! item.name could be 'xxx.xxx'
- const ov = newProps[item.name];
- const v = item.initial(node as any, getRealValue(ov));
- if (ov === undefined && v !== undefined) {
- newProps[item.name] = v;
- }
- } catch (e) {
- if (hasOwnProperty(props, item.name)) {
- newProps[item.name] = props[item.name];
- }
- }
- if (newProps[item.name] && !node.props.has(item.name)) {
- node.props.add(newProps[item.name], item.name);
- }
- });
- }
- return newProps;
-}, TransformStage.Init);
+designer.addPropsReducer(liveLifecycleReducer, TransformStage.Render);
-designer.addPropsReducer((props: any, node: Node) => {
- if (node.isRoot() && props && props.lifeCycles) {
- return {
- ...props,
- lifeCycles: {},
- };
- }
- return props;
-}, TransformStage.Render);
-
-function filterReducer(props: any, node: Node): any {
- const filters = node.componentMeta.getMetadata().experimental?.filters;
- if (filters && filters.length) {
- const newProps = { ...props };
- filters.forEach((item) => {
- // FIXME! item.name could be 'xxx.xxx'
- if (!hasOwnProperty(newProps, item.name)) {
- return;
- }
- try {
- if (item.filter(node.settingEntry.getProp(item.name), props[item.name]) === false) {
- delete newProps[item.name];
- }
- } catch (e) {
- console.warn(e);
- logger.trace(e);
- }
- });
- return newProps;
- }
- return props;
-}
designer.addPropsReducer(filterReducer, TransformStage.Save);
designer.addPropsReducer(filterReducer, TransformStage.Render);
-function compatiableReducer(props: any) {
- if (!props || !isPlainObject(props)) {
- return props;
- }
- if (isJSSlot(props)) {
- return {
- type: 'JSBlock',
- value: {
- componentName: 'Slot',
- children: props.value,
- props: {
- slotTitle: props.title,
- slotName: props.name,
- },
- },
- };
- }
- // 为了能降级到老版本,建议在后期版本去掉以下代码
- // if (isJSExpression(props) && !props.events) {
- // return {
- // type: 'variable',
- // value: props.mock,
- // variable: props.value,
- // }
- // }
- const newProps: any = {};
- Object.entries(props).forEach(([key, val]) => {
- newProps[key] = compatiableReducer(val);
- });
- return newProps;
-}
// FIXME: Dirty fix, will remove this reducer
-designer.addPropsReducer(compatiableReducer, TransformStage.Save);
+designer.addPropsReducer(compatibleReducer, TransformStage.Save);
// 兼容历史版本的 Page 组件
-designer.addPropsReducer((props: any, node: Node) => {
- const lifeCycleNames = ['didMount', 'willUnmount'];
- if (node.isRoot()) {
- lifeCycleNames.forEach(key => {
- if (props[key]) {
- const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {};
- lifeCycles[key] = props[key];
- node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles);
- }
- });
- }
- return props;
-}, TransformStage.Save);
+designer.addPropsReducer(upgradePageLifeCyclesReducer, TransformStage.Save);
// 设计器组件样式处理
-function stylePropsReducer(props: any, node: any) {
- if (props && typeof props === 'object' && props.__style__) {
- const cssId = `_style_pesudo_${ node.id.replace(/\$/g, '_')}`;
- const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
- const styleProp = props.__style__;
- appendStyleNode(props, styleProp, cssClass, cssId);
- }
- if (props && typeof props === 'object' && props.pageStyle) {
- const cssId = '_style_pesudo_engine-document';
- const cssClass = 'engine-document';
- const styleProp = props.pageStyle;
- appendStyleNode(props, styleProp, cssClass, cssId);
- }
- if (props && typeof props === 'object' && props.containerStyle) {
- const cssId = `_style_pesudo_${ node.id}`;
- const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
- const styleProp = props.containerStyle;
- appendStyleNode(props, styleProp, cssClass, cssId);
- }
- return props;
-}
-
-function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: string) {
- const doc = designer.currentDocument?.simulator?.contentDocument;
- if (!doc) {
- return;
- }
- const dom = doc.getElementById(cssId);
- if (dom) {
- dom.parentNode?.removeChild(dom);
- }
- if (typeof styleProp === 'object') {
- styleProp = toCss(styleProp);
- }
- if (typeof styleProp === 'string') {
- const s = doc.createElement('style');
- props.className = cssClass;
- s.setAttribute('type', 'text/css');
- s.setAttribute('id', cssId);
- doc.getElementsByTagName('head')[0].appendChild(s);
-
- s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => {
- return `${b / 2}px`;
- }).replace(/:root/g, `.${ cssClass}`)));
- }
-}
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
-
// 国际化 & Expression 渲染时处理
designer.addPropsReducer(deepValueParser, TransformStage.Render);
+// Init 的时候没有拿到 dataSource, 只能在 Render 和 Save 的时候都调用一次,理论上执行时机在 Init
+// Render 和 Save 都要各调用一次,感觉也是有问题的,是不是应该在 Render 执行一次就行了?见上 filterReducer 也是一样的处理方式。
+designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Render);
+designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Save);
+
+designer.addPropsReducer(nodeTopFixedReducer, TransformStage.Render);
+designer.addPropsReducer(nodeTopFixedReducer, TransformStage.Save);
+
skeleton.add({
area: 'mainArea',
name: 'designer',
@@ -283,6 +80,9 @@ skeleton.add({
name: 'settingsPane',
type: 'Panel',
content: SettingsPrimaryPane,
+ props: {
+ ignoreRoot: true,
+ },
});
skeleton.add({
area: 'leftArea',
diff --git a/packages/editor-preset-vision/src/env.ts b/packages/editor-preset-vision/src/env.ts
index 4e267ee24..786c1f74a 100644
--- a/packages/editor-preset-vision/src/env.ts
+++ b/packages/editor-preset-vision/src/env.ts
@@ -80,6 +80,11 @@ export class Env {
};
}
+ clear() {
+ this.envs = {};
+ this.featureMap = {};
+ }
+
getAliSchemaVersion() {
return ALI_SCHEMA_VERSION;
}
diff --git a/packages/editor-preset-vision/src/flags.ts b/packages/editor-preset-vision/src/flags.ts
index 5c5aefec3..2a68db857 100644
--- a/packages/editor-preset-vision/src/flags.ts
+++ b/packages/editor-preset-vision/src/flags.ts
@@ -108,14 +108,14 @@ export class Flags {
return;
}
- const doe = document.documentElement;
+ const doc = document.documentElement;
if (this.lastFlags) {
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
- doe.classList.remove(`engine-${flag}`);
+ doc.classList.remove(`engine-${flag}`);
});
}
this.flags.forEach((flag) => {
- doe.classList.add(`engine-${flag}`);
+ doc.classList.add(`engine-${flag}`);
});
this.lastFlags = this.flags.slice(0);
diff --git a/packages/editor-preset-vision/src/index.ts b/packages/editor-preset-vision/src/index.ts
index 30a3ddef1..f27e95045 100644
--- a/packages/editor-preset-vision/src/index.ts
+++ b/packages/editor-preset-vision/src/index.ts
@@ -5,6 +5,7 @@ import logger from '@ali/vu-logger';
import { render } from 'react-dom';
import I18nUtil from './i18n-util';
import { hotkey as Hotkey, monitor } from '@ali/lowcode-editor-core';
+import { registerMetadataTransducer } from '@ali/lowcode-designer';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
import Bus from './bus';
@@ -14,6 +15,7 @@ import Panes from './panes';
import Exchange from './exchange';
import context from './context';
import VisualManager from './base/visualManager';
+import VisualDesigner from './base/visualDesigner';
import Trunk from './bundle/trunk';
import Prototype from './bundle/prototype';
import Bundle from './bundle/bundle';
@@ -22,10 +24,11 @@ import * as Field from './fields';
import Prop from './prop';
import Env from './env';
import DragEngine from './drag-engine';
+// import Flags from './base/flags';
import Viewport from './viewport';
import Project from './project';
-
import Symbols from './symbols';
+import '@ali/lowcode-editor-setters';
import './vision.less';
@@ -60,6 +63,7 @@ const ui = {
const modules = {
VisualManager,
+ VisualDesigner,
I18nUtil,
Prop,
};
@@ -107,6 +111,8 @@ const VisualEngine = {
Project,
logger,
Symbols,
+ registerMetadataTransducer,
+ // Flags,
};
(window as any).VisualEngine = VisualEngine;
@@ -156,12 +162,18 @@ export {
Project,
logger,
Symbols,
+ registerMetadataTransducer,
};
-const version = '6.0.0(LowcodeEngine 0.9.3)';
+const version = '6.0.0 (LowcodeEngine 0.9.32)';
console.log(
`%c VisionEngine %c v${version} `,
+<<<<<<< HEAD
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060;font-weight:bold;',
'padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e;font-weight:bold;',
+=======
+ 'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;',
+ 'padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e; font-weight: bold;',
+>>>>>>> origin/refactor/vision-code-split
);
diff --git a/packages/editor-preset-vision/src/pages.ts b/packages/editor-preset-vision/src/pages.ts
index 5617e882c..95dfa0e64 100644
--- a/packages/editor-preset-vision/src/pages.ts
+++ b/packages/editor-preset-vision/src/pages.ts
@@ -32,27 +32,46 @@ const pages = Object.assign(project, {
if (!pages || !Array.isArray(pages) || pages.length === 0) {
throw new Error('pages schema 不合法');
}
-
- let componentsTree: any;
- if (isPageDataV1(pages[0])) {
- componentsTree = [pages[0].layout];
+ // todo: miniapp
+ let componentsTree: any = [];
+ if (window.pageConfig?.isNoCodeMiniApp) {
+ // 小程序多页面
+ pages.forEach((item: any) => {
+ if (isPageDataV1(item)) {
+ componentsTree.push(item.layout);
+ } else {
+ componentsTree.push(item.componentsTree[0]);
+ }
+ });
} else {
- componentsTree = pages[0].componentsTree;
- if (componentsTree[0]) {
- componentsTree[0].componentName = componentsTree[0].componentName || 'Page';
- // FIXME
- if (componentsTree[0].componentName === 'Page' || componentsTree[0].componentName === 'Component') {
- componentsTree[0].methods = {};
+ if (isPageDataV1(pages[0])) {
+ componentsTree = [pages[0].layout];
+ } else {
+ // if (!pages[0].componentsTree) return;
+ componentsTree = pages[0].componentsTree;
+ if (componentsTree[0]) {
+ componentsTree[0].componentName = componentsTree[0].componentName || 'Page';
+ // FIXME
+ if (componentsTree[0].componentName === 'Page' || componentsTree[0].componentName === 'Component') {
+ componentsTree[0].methods = {};
+ }
}
}
}
+ componentsTree.forEach((item: any) => {
+ item.componentName = item.componentName || 'Page';
+ if (item.componentName === 'Page' || item.componentName === 'Component') {
+ item.methods = {};
+ }
+ });
project.load(
{
version: '1.0.0',
componentsMap: [],
componentsTree,
id: pages[0].id,
+ config: project.config,
},
true,
);
@@ -105,6 +124,9 @@ Object.defineProperty(pages, 'currentPage', {
get() {
return project.currentDocument;
},
+ set(_currentPage) {
+ // do nothing
+ },
});
pages.onCurrentPageChange((page: DocumentModel) => {
diff --git a/packages/editor-preset-vision/src/panes.ts b/packages/editor-preset-vision/src/panes.ts
index 8c0776cab..840211188 100644
--- a/packages/editor-preset-vision/src/panes.ts
+++ b/packages/editor-preset-vision/src/panes.ts
@@ -47,6 +47,8 @@ export interface OldPaneConfig {
index?: number; // todo
isAction?: boolean; // as normal dock
fullScreen?: boolean; // todo
+ canSetFixed?: boolean; // 是否可以设置固定模式
+ defaultFixed?: boolean; // 是否默认固定
}
function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: string } {
@@ -64,11 +66,24 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
contentProps: props,
index: index || props?.index,
};
+
if (type === 'dock') {
newConfig.type = 'PanelDock';
newConfig.area = 'left';
newConfig.props.description = description || title;
- const { contents, hideTitleBar, tip, width, maxWidth, height, maxHeight, menu, isAction, canSetFixed } = config;
+ const {
+ contents,
+ hideTitleBar,
+ tip,
+ width,
+ maxWidth,
+ height,
+ maxHeight,
+ menu,
+ isAction,
+ canSetFixed,
+ defaultFixed,
+ } = config;
if (menu) {
newConfig.props.title = menu;
}
@@ -84,10 +99,12 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
height,
maxHeight,
canSetFixed,
- onInit: init,
- onDestroy: destroy,
};
+ if (defaultFixed) {
+ newConfig.panelProps.area = 'leftFixedArea';
+ }
+
if (contents && Array.isArray(contents)) {
newConfig.content = contents.map(({ title, content, tip }, index) => {
return {
@@ -103,23 +120,21 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
});
}
}
+ } else if (type === 'action') {
+ newConfig.area = 'top';
+ newConfig.type = 'Dock';
+ } else if (type === 'tab') {
+ newConfig.area = 'right';
+ newConfig.type = 'Panel';
+ } else if (type === 'stage') {
+ newConfig.area = 'stages';
+ newConfig.type = 'Widget';
} else {
- newConfig.props.onInit = init;
- newConfig.props.onDestroy = destroy;
- if (type === 'action') {
- newConfig.area = 'top';
- newConfig.type = 'Dock';
- } else if (type === 'tab') {
- newConfig.area = 'right';
- newConfig.type = 'Panel';
- } else if (type === 'stage') {
- newConfig.area = 'stages';
- newConfig.type = 'Widget';
- } else {
- newConfig.area = 'main';
- newConfig.type = 'Widget';
- }
+ newConfig.area = 'main';
+ newConfig.type = 'Widget';
}
+ newConfig.props.onInit = init;
+ newConfig.props.onDestroy = destroy;
return newConfig;
}
diff --git a/packages/editor-preset-vision/src/project.ts b/packages/editor-preset-vision/src/project.ts
index 9239a9497..2d978c748 100644
--- a/packages/editor-preset-vision/src/project.ts
+++ b/packages/editor-preset-vision/src/project.ts
@@ -1,17 +1,19 @@
-export class Project {
- private schema: any;
+import { designer } from './editor';
- constructor() {
- this.schema = {};
- }
+const { project } = designer;
- getSchema() {
- return this.schema;
- }
+Object.assign(project, {
+ getSchema(): any {
+ return this.schema || {};
+ },
setSchema(schema: any) {
this.schema = schema;
- }
-}
+ },
-export default new Project();
+ setConfig(config: any) {
+ this.set('config', config);
+ },
+});
+
+export default project;
diff --git a/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts
new file mode 100644
index 000000000..65186ea81
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts
@@ -0,0 +1,36 @@
+import {
+ isPlainObject,
+} from '@ali/lowcode-utils';
+import { isJSExpression, isJSSlot } from '@ali/lowcode-types';
+
+export function compatibleReducer(props: any) {
+ if (!props || !isPlainObject(props)) {
+ return props;
+ }
+ // 为了能降级到老版本,建议在后期版本去掉以下代码
+ if (isJSSlot(props)) {
+ return {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ children: props.value,
+ props: {
+ slotTitle: props.title,
+ slotName: props.name,
+ },
+ },
+ };
+ }
+ if (isJSExpression(props) && !props.events) {
+ return {
+ type: 'variable',
+ value: props.mock,
+ variable: props.value,
+ };
+ }
+ const newProps: any = {};
+ Object.entries(props).forEach(([key, val]) => {
+ newProps[key] = compatibleReducer(val);
+ });
+ return newProps;
+}
diff --git a/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts
new file mode 100644
index 000000000..928e8dd82
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts
@@ -0,0 +1,25 @@
+import logger from '@ali/vu-logger';
+import { hasOwnProperty } from '@ali/lowcode-utils';
+
+export function filterReducer(props: any, node: Node): any {
+ const filters = node.componentMeta.getMetadata().experimental?.filters;
+ if (filters && filters.length) {
+ const newProps = { ...props };
+ filters.forEach((item) => {
+ // FIXME! item.name could be 'xxx.xxx'
+ if (!hasOwnProperty(newProps, item.name)) {
+ return;
+ }
+ try {
+ if (item.filter(node.settingEntry.getProp(item.name), props[item.name]) === false) {
+ delete newProps[item.name];
+ }
+ } catch (e) {
+ console.warn(e);
+ logger.trace(e);
+ }
+ });
+ return newProps;
+ }
+ return props;
+}
\ No newline at end of file
diff --git a/packages/editor-preset-vision/src/props-reducers/index.ts b/packages/editor-preset-vision/src/props-reducers/index.ts
new file mode 100644
index 000000000..148d115b7
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/index.ts
@@ -0,0 +1,8 @@
+export * from './downgrade-schema-reducer';
+export * from './filter-reducer';
+export * from './init-node-reducer';
+export * from './live-lifecycle-reducer';
+export * from './remove-empty-prop-reducer';
+export * from './style-reducer';
+export * from './upgrade-reducer';
+export * from './node-top-fixed-reducer';
diff --git a/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts
new file mode 100644
index 000000000..031482f46
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts
@@ -0,0 +1,68 @@
+import {
+ hasOwnProperty,
+ isI18NObject,
+ isUseI18NSetter,
+ convertToI18NObject,
+ isString,
+} from '@ali/lowcode-utils';
+import { isJSExpression, isJSBlock, isJSSlot } from '@ali/lowcode-types';
+import { isVariable, getCurrentFieldIds } from '../utils';
+
+export function initNodeReducer(props, node) {
+ // run initials
+ const newProps: any = {
+ ...props,
+ };
+ if (newProps.fieldId) {
+ const fieldIds = getCurrentFieldIds();
+
+ // 全局的关闭 uniqueIdChecker 信号,在 ve-utils 中实现
+ if (fieldIds.indexOf(props.fieldId) >= 0 && !(window as any).__disable_unique_id_checker__) {
+ newProps.fieldId = undefined;
+ }
+ }
+ const initials = node.componentMeta.getMetadata().experimental?.initials;
+
+ if (initials) {
+ const getRealValue = (propValue: any) => {
+ if (isVariable(propValue)) {
+ return propValue.value;
+ }
+ if (isJSExpression(propValue)) {
+ return propValue.mock;
+ }
+ return propValue;
+ };
+ initials.forEach(item => {
+ // FIXME! this implements SettingTarget
+ try {
+ // FIXME! item.name could be 'xxx.xxx'
+ const ov = newProps[item.name];
+ const v = item.initial(node as any, getRealValue(ov));
+ if (ov === undefined && v !== undefined) {
+ newProps[item.name] = v;
+ }
+ // 兼容 props 中的属性为 i18n 类型,但是仅提供了一个字符串值,非变量绑定
+ if (
+ isUseI18NSetter(node.componentMeta.prototype, item.name) &&
+ !isI18NObject(ov) &&
+ !isJSExpression(ov) &&
+ !isJSBlock(ov) &&
+ !isJSSlot(ov) &&
+ !isVariable(ov) &&
+ (isString(v) || isI18NObject(v))
+ ) {
+ newProps[item.name] = convertToI18NObject(v);
+ }
+ } catch (e) {
+ if (hasOwnProperty(props, item.name)) {
+ newProps[item.name] = props[item.name];
+ }
+ }
+ if (newProps[item.name] && !node.props.has(item.name)) {
+ node.props.add(newProps[item.name], item.name, false, { skipSetSlot: true });
+ }
+ });
+ }
+ return newProps;
+}
diff --git a/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts
new file mode 100644
index 000000000..a626c3b30
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts
@@ -0,0 +1,27 @@
+import { globalContext, Editor } from '@ali/lowcode-editor-core';
+import { Node } from '@ali/lowcode-designer';
+
+export function liveLifecycleReducer(props: any, node: Node) {
+ const editor = globalContext.get(Editor);
+ // live 模式下解析 lifeCycles
+ if (node.isRoot() && props && props.lifeCycles) {
+ if (editor.get('designMode') === 'live') {
+ const lifeCycleMap = {
+ didMount: 'componentDidMount',
+ willUnmount: 'componentWillUnMount',
+ };
+ const lifeCycles = props.lifeCycles;
+ Object.keys(lifeCycleMap).forEach(key => {
+ if (lifeCycles[key]) {
+ lifeCycles[lifeCycleMap[key]] = lifeCycles[key];
+ }
+ });
+ return props;
+ }
+ return {
+ ...props,
+ lifeCycles: {},
+ };
+ }
+ return props;
+}
\ No newline at end of file
diff --git a/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts b/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts
new file mode 100644
index 000000000..194496a9c
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts
@@ -0,0 +1,12 @@
+import { Node } from '@ali/lowcode-designer';
+
+export function nodeTopFixedReducer(props: any, node: Node) {
+ if (node.componentMeta.isTopFixed) {
+ return {
+ ...props,
+ // experimental prop value
+ __isTopFixed__: true,
+ };
+ }
+ return props;
+}
\ No newline at end of file
diff --git a/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts
new file mode 100644
index 000000000..305f0bdb7
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts
@@ -0,0 +1,23 @@
+import {
+ cloneDeep,
+} from '@ali/lowcode-utils';
+
+// 清除空的 props value
+export function removeEmptyPropsReducer(props: any, node: Node) {
+ if (node.isRoot() && props.dataSource && Array.isArray(props.dataSource.online)) {
+ const online = cloneDeep(props.dataSource.online);
+ online.forEach((item: any) => {
+ const newParam: any = {};
+ if (Array.isArray(item?.options?.params)) {
+ item.options.params.forEach((element: any) => {
+ if (element.name) {
+ newParam[element.name] = element.value;
+ }
+ });
+ item.options.params = newParam;
+ }
+ });
+ props.dataSource.list = online;
+ }
+ return props;
+}
diff --git a/packages/editor-preset-vision/src/props-reducers/style-reducer.ts b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts
new file mode 100644
index 000000000..214d8deb1
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts
@@ -0,0 +1,50 @@
+import { globalContext, Editor } from '@ali/lowcode-editor-core';
+import { toCss } from '@ali/vu-css-style';
+
+export function stylePropsReducer(props: any, node: any) {
+ if (props && typeof props === 'object' && props.__style__) {
+ const cssId = `_style_pesudo_${ node.id.replace(/\$/g, '_')}`;
+ const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
+ const styleProp = props.__style__;
+ appendStyleNode(props, styleProp, cssClass, cssId);
+ }
+ if (props && typeof props === 'object' && props.pageStyle) {
+ const cssId = '_style_pesudo_engine-document';
+ const cssClass = 'engine-document';
+ const styleProp = props.pageStyle;
+ appendStyleNode(props, styleProp, cssClass, cssId);
+ }
+ if (props && typeof props === 'object' && props.containerStyle) {
+ const cssId = `_style_pesudo_${ node.id}`;
+ const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
+ const styleProp = props.containerStyle;
+ appendStyleNode(props, styleProp, cssClass, cssId);
+ }
+ return props;
+}
+
+function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: string) {
+ const editor = globalContext.get(Editor);
+ const designer = editor.get('designer');
+ const doc = designer.currentDocument?.simulator?.contentDocument;
+ if (!doc) {
+ return;
+ }
+ const dom = doc.getElementById(cssId);
+ if (dom) {
+ dom.parentNode?.removeChild(dom);
+ }
+ if (typeof styleProp === 'object') {
+ styleProp = toCss(styleProp);
+ }
+ if (typeof styleProp === 'string') {
+ const s = doc.createElement('style');
+ props.className = cssClass;
+ s.setAttribute('type', 'text/css');
+ s.setAttribute('id', cssId);
+ doc.getElementsByTagName('head')[0].appendChild(s);
+ s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => {
+ return `${b / 2}px`;
+ }).replace(/:root/g, `.${cssClass}`)));
+ }
+}
\ No newline at end of file
diff --git a/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts
new file mode 100644
index 000000000..abe93a454
--- /dev/null
+++ b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts
@@ -0,0 +1,53 @@
+import { getConvertedExtraKey } from '@ali/lowcode-designer';
+import {
+ isPlainObject,
+} from '@ali/lowcode-utils';
+import { isJSBlock } from '@ali/lowcode-types';
+import { isVariable } from '../utils';
+
+export function upgradePropsReducer(props: any) {
+ if (!props || !isPlainObject(props)) {
+ return props;
+ }
+ if (isJSBlock(props)) {
+ if (props.value.componentName === 'Slot') {
+ return {
+ type: 'JSSlot',
+ title: (props.value.props as any)?.slotTitle,
+ name: (props.value.props as any)?.slotName,
+ value: props.value.children,
+ };
+ } else {
+ return props.value;
+ }
+ }
+ if (isVariable(props)) {
+ return {
+ type: 'JSExpression',
+ value: props.variable,
+ mock: props.value,
+ };
+ }
+ const newProps: any = {};
+ Object.keys(props).forEach((key) => {
+ if (/^__slot__/.test(key) && props[key] === true) {
+ return;
+ }
+ newProps[key] = upgradePropsReducer(props[key]);
+ });
+ return newProps;
+}
+
+export function upgradePageLifeCyclesReducer(props: any, node: Node) {
+ const lifeCycleNames = ['didMount', 'willUnmount'];
+ if (node.isRoot()) {
+ lifeCycleNames.forEach(key => {
+ if (props[key]) {
+ const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {};
+ lifeCycles[key] = props[key];
+ node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles);
+ }
+ });
+ }
+ return props;
+}
diff --git a/packages/editor-preset-vision/src/utils/index.ts b/packages/editor-preset-vision/src/utils/index.ts
new file mode 100644
index 000000000..ec3d7ea31
--- /dev/null
+++ b/packages/editor-preset-vision/src/utils/index.ts
@@ -0,0 +1,25 @@
+import { globalContext, Editor } from '@ali/lowcode-editor-core';
+
+interface Variable {
+ type: 'variable';
+ variable: string;
+ value: any;
+}
+
+export function isVariable(obj: any): obj is Variable {
+ return obj && obj.type === 'variable';
+}
+
+export function getCurrentFieldIds() {
+ const editor = globalContext.get(Editor);
+ const designer = editor.get('designer');
+ const fieldIds: any = [];
+ const nodesMap = designer?.currentDocument?.nodesMap || new Map();
+ nodesMap.forEach((curNode: any) => {
+ const fieldId = nodesMap?.get(curNode.id)?.getPropValue('fieldId');
+ if (fieldId) {
+ fieldIds.push(fieldId);
+ }
+ });
+ return fieldIds;
+}
diff --git a/packages/editor-preset-vision/src/viewport.ts b/packages/editor-preset-vision/src/viewport.ts
index da943bed1..c21b29f02 100644
--- a/packages/editor-preset-vision/src/viewport.ts
+++ b/packages/editor-preset-vision/src/viewport.ts
@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import Flags from './flags';
-import { designer } from './editor';
+import { editor } from './editor';
const domReady = require('domready');
@@ -58,7 +58,6 @@ class StyleResource {
this.inited = true;
const { type, content } = this.config;
-
let styleElement: any;
if (type === 'URL') {
styleElement = document.createElement('link');
@@ -192,10 +191,11 @@ export class Viewport {
return this.preview;
}
- setDevice(device = 'pc') {
+ async setDevice(device = 'pc') {
if (this.getDevice() !== device) {
this.device = device;
- designer.currentDocument?.simulator?.set('device', device === 'mobile' ? 'mobile' : 'default');
+ const simulator = await editor.onceGot('simulator');
+ simulator?.set('device', device === 'mobile' ? 'mobile' : 'default');
// Flags.setSimulator(device);
// this.applyMediaCSS();
this.emitter.emit('devicechange', device);
diff --git a/packages/editor-preset-vision/src/vision.less b/packages/editor-preset-vision/src/vision.less
index 08be75898..fdf1c737b 100644
--- a/packages/editor-preset-vision/src/vision.less
+++ b/packages/editor-preset-vision/src/vision.less
@@ -103,9 +103,9 @@ html.engine-blur #engine {
height: 16px!important;
}
-.lc-left-float-pane {
- font-size: 14px;
-}
+// .lc-left-float-pane {
+// font-size: 14px;
+// }
html.engine-preview-mode {
.lc-left-area, .lc-right-area {
@@ -114,7 +114,7 @@ html.engine-preview-mode {
}
.ve-popups .ve-message {
- right: 290px;
+ right: calc(var(--right-area-width, 300px) + 10px);
.ve-message-content {
display: flex;
@@ -125,4 +125,4 @@ html.engine-preview-mode {
.lc-setter-mixed {
flex: 1
-}
\ No newline at end of file
+}
diff --git a/packages/editor-preset-vision/tests/bundle/bundle.test.ts b/packages/editor-preset-vision/tests/bundle/bundle.test.ts
new file mode 100644
index 000000000..31174d481
--- /dev/null
+++ b/packages/editor-preset-vision/tests/bundle/bundle.test.ts
@@ -0,0 +1,116 @@
+import { Component } from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import divPrototypeConfig from '../fixtures/prototype/div-vision';
+import trunk from '../../src/bundle/trunk';
+import Prototype from '../../src/bundle/prototype';
+import Bundle from '../../src/bundle/bundle';
+import { Editor } from '@ali/lowcode-editor-core';
+
+jest.mock('../../src/bundle/trunk', () => {
+ // mockComponentPrototype = jest.fn();
+ // return {
+ // mockComponentPrototype: jest.fn().mockImplementation(() => {
+ // return proto;
+ // }),
+ // }
+ // return jest.fn().mockImplementation(() => {
+ // return {playSoundFile: fakePlaySoundFile};
+ // });
+ // return jest.fn().mockImplementation(() => {
+ // return { mockComponentPrototype };
+ // });
+ return {
+ __esModule: true,
+ default: {
+ mockComponentPrototype: jest.fn(),
+ },
+ };
+});
+
+function wrap(name, thing) {
+ return {
+ name,
+ componentName: name,
+ category: '布局',
+ module: thing,
+ };
+}
+
+const proto1 = new Prototype(divPrototypeConfig);
+const protoConfig2 = cloneDeep(divPrototypeConfig);
+set(protoConfig2, 'componentName', 'Div2');
+const proto2 = new Prototype(protoConfig2);
+
+const protoConfig3 = cloneDeep(divPrototypeConfig);
+set(protoConfig3, 'componentName', 'Div3');
+const proto3 = new Prototype(protoConfig3);
+
+const protoConfig4 = cloneDeep(divPrototypeConfig);
+set(protoConfig4, 'componentName', 'Div4');
+const proto4 = new Prototype(protoConfig4);
+
+const protoConfig5 = cloneDeep(divPrototypeConfig);
+set(protoConfig5, 'componentName', 'Div5');
+const proto5 = new Prototype(protoConfig5);
+
+function getComponentProtos() {
+ return [
+ wrap('Div', proto1),
+ // wrap('Div2', proto2),
+ // wrap('Div3', proto3),
+ wrap('DivPortal', [proto2, proto3]),
+ ];
+}
+
+class Div extends Component {}
+Div.displayName = 'Div';
+class Div2 extends Component {}
+Div2.displayName = 'Div2';
+class Div3 extends Component {}
+Div3.displayName = 'Div3';
+class Div4 extends Component {}
+Div4.displayName = 'Div4';
+class Div5 extends Component {}
+Div5.displayName = 'Div5';
+
+function getComponentViews() {
+ return [
+ wrap('Div', Div),
+ // wrap('Div2', Div2),
+ // wrap('Div3', Div3),
+ wrap('DivPortal', [Div2, Div3]),
+ ];
+}
+
+describe('Bundle', () => {
+ it('构造函数', () => {
+ const protos = getComponentProtos();
+ const views = getComponentViews();
+ const bundle = new Bundle(protos, views);
+ expect(bundle.getList()).toHaveLength(3);
+ expect(bundle.get('Div')).toBe(proto1);
+ expect(bundle.get('Div2')).toBe(proto2);
+ expect(bundle.get('Div3')).toBe(proto3);
+ bundle.addComponentBundle([proto4, Div4]);
+ expect(bundle.getList()).toHaveLength(4);
+ expect(bundle.get('Div4')).toBe(proto4);
+ bundle.replacePrototype('Div4', proto3);
+ expect(proto3.getView()).toBe(Div4);
+
+ bundle.removeComponentBundle('Div2');
+ expect(bundle.getList()).toHaveLength(3);
+ expect(bundle.get('Div2')).toBeUndefined;
+
+ expect(bundle.getFromMeta('Div')).toBe(proto1);
+ bundle.getFromMeta('Div5');
+ expect(bundle.getList()).toHaveLength(4);
+ });
+ it('静态方法 create', () => {
+ const protos = getComponentProtos();
+ const views = getComponentViews();
+ const bundle = Bundle.create(protos, views);
+ expect(bundle).toBeTruthy();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/bundle/prototype.test.ts b/packages/editor-preset-vision/tests/bundle/prototype.test.ts
new file mode 100644
index 000000000..4776ebb1e
--- /dev/null
+++ b/packages/editor-preset-vision/tests/bundle/prototype.test.ts
@@ -0,0 +1,219 @@
+import { Component } from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Designer } from '../../src/designer/designer';
+import divPrototypeConfig from '../fixtures/prototype/div-vision';
+import divFullPrototypeConfig from '../fixtures/prototype/div-vision-full';
+import divPrototypeMeta from '../fixtures/prototype/div-meta';
+// import VisualEngine from '../../src';
+import { designer } from '../../src/editor';
+import Prototype, { isPrototype } from '../../src/bundle/prototype';
+import { Editor } from '@ali/lowcode-editor-core';
+// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+
+describe('Prototype', () => {
+ it('构造函数 - OldPrototypeConfig', () => {
+ const proto = new Prototype(divPrototypeConfig);
+ expect(isPrototype(proto)).toBeTruthy;
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getCategory()).toBe('布局');
+ expect(proto.getDocUrl()).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getIcon()).toBeUndefined;
+ expect(proto.getTitle()).toBe('Div');
+ expect(proto.isPrototype).toBeTruthy;
+ expect(proto.isContainer()).toBeTruthy;
+ expect(proto.isModal()).toBeFalsy;
+ });
+ it('构造函数 - 全量 OldPrototypeConfig', () => {
+ const proto = new Prototype(divFullPrototypeConfig);
+ expect(isPrototype(proto)).toBeTruthy;
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getCategory()).toBe('布局');
+ expect(proto.getDocUrl()).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getIcon()).toBeUndefined;
+ expect(proto.getTitle()).toBe('Div');
+ expect(proto.isPrototype).toBeTruthy;
+ expect(proto.isContainer()).toBeTruthy;
+ expect(proto.isModal()).toBeFalsy;
+ });
+ it('构造函数 - ComponentMetadata', () => {
+ const proto = new Prototype(divPrototypeMeta);
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getCategory()).toBe('布局');
+ expect(proto.getDocUrl()).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getIcon()).toBeUndefined;
+ expect(proto.getTitle()).toBe('Div');
+ expect(proto.isPrototype).toBeTruthy;
+ expect(proto.isContainer()).toBeTruthy;
+ expect(proto.isModal()).toBeFalsy;
+ });
+ it('构造函数 - ComponentMeta', () => {
+ const meta = designer.createComponentMeta(divPrototypeMeta);
+ const proto = new Prototype(meta);
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getCategory()).toBe('布局');
+ expect(proto.getDocUrl()).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getIcon()).toBeUndefined;
+ expect(proto.getTitle()).toBe('Div');
+ expect(proto.isPrototype).toBeTruthy;
+ expect(proto.isContainer()).toBeTruthy;
+ expect(proto.isModal()).toBeFalsy;
+ });
+ it('构造函数 - 静态函数 create', () => {
+ const proto = Prototype.create(divPrototypeConfig);
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getCategory()).toBe('布局');
+ expect(proto.getDocUrl()).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getIcon()).toBeUndefined;
+ expect(proto.getTitle()).toBe('Div');
+ expect(proto.isPrototype).toBeTruthy;
+ expect(proto.isContainer()).toBeTruthy;
+ expect(proto.isModal()).toBeFalsy;
+ });
+ it('构造函数 - lookup: true', () => {
+ const proto = Prototype.create(divPrototypeConfig);
+ const proto2 = Prototype.create(divPrototypeConfig, {}, true);
+ expect(proto).toBe(proto2);
+ });
+ describe('类成员函数', () => {
+ let proto: Prototype = null;
+ beforeEach(() => {
+ proto = new Prototype(divPrototypeConfig);
+ });
+ afterEach(() => {
+ proto = null;
+ });
+ it('各种函数', () => {
+ expect(proto.componentName).toBe('Div');
+ expect(proto.getComponentName()).toBe('Div');
+ expect(proto.getId()).toBe('Div');
+ expect(proto.getContextInfo('anything')).toBeUndefined;
+ expect(proto.getPropConfigs()).toBe(divPrototypeConfig);
+ expect(proto.getConfig()).toBe(divPrototypeConfig);
+ expect(proto.getConfig('componentName')).toBe('Div');
+ expect(proto.getConfig('configure')).toBe(divPrototypeConfig.configure);
+ expect(proto.getConfig('docUrl')).toBe(
+ 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ );
+ expect(proto.getConfig('title')).toBe('容器');
+ expect(proto.getConfig('isContainer')).toBeTruthy;
+
+ class MockView extends Component {}
+
+ expect(proto.getView()).toBeUndefined;
+ proto.setView(MockView);
+ expect(proto.getView()).toBe(MockView);
+ expect(proto.meta.getMetadata().experimental?.view).toBe(MockView);
+
+ expect(proto.getPackageName()).toBeUndefined;
+ proto.setPackageName('@ali/vc-div');
+ expect(proto.getPackageName()).toBe('@ali/vc-div');
+
+ expect(proto.getConfig('category')).toBe('布局');
+ proto.setCategory('布局 new');
+ expect(proto.getConfig('category')).toBe('布局 new');
+
+ expect(proto.getConfigure()).toHaveLength(3);
+ expect(proto.getConfigure()[0].name).toBe('#props');
+ expect(proto.getConfigure()[1].name).toBe('#styles');
+ expect(proto.getConfigure()[2].name).toBe('#advanced');
+
+ expect(proto.getRectSelector()).toBeUndefined;
+ expect(proto.isAutoGenerated()).toBeFalsy;
+ });
+ });
+
+ describe('类成员函数', () => {
+ it('addGlobalPropsConfigure', () => {
+ Prototype.addGlobalPropsConfigure({
+ name: 'globalInsertProp1',
+ });
+ const proto1 = new Prototype(divPrototypeConfig);
+ expect(proto1.getConfigure()[2].items).toHaveLength(4);
+ expect(proto1.getConfigure()[2].items[3].name).toBe('globalInsertProp1');
+ Prototype.addGlobalPropsConfigure({
+ name: 'globalInsertProp2',
+ });
+ const proto2 = new Prototype(divPrototypeConfig);
+ expect(proto2.getConfigure()[2].items).toHaveLength(5);
+ expect(proto1.getConfigure()[2].items[4].name).toBe('globalInsertProp2');
+
+ Prototype.addGlobalPropsConfigure({
+ name: 'globalInsertProp3',
+ position: 'top',
+ });
+
+ const proto3 = new Prototype(divPrototypeConfig);
+ expect(proto3.getConfigure()[0].items).toHaveLength(3);
+ expect(proto1.getConfigure()[0].items[0].name).toBe('globalInsertProp3');
+ });
+
+ it('removeGlobalPropsConfigure', () => {
+ Prototype.removeGlobalPropsConfigure('globalInsertProp1');
+ Prototype.removeGlobalPropsConfigure('globalInsertProp2');
+ Prototype.removeGlobalPropsConfigure('globalInsertProp3');
+ const proto2 = new Prototype(divPrototypeConfig);
+ expect(proto2.getConfigure()[0].items).toHaveLength(2);
+ expect(proto2.getConfigure()[2].items).toHaveLength(3);
+ });
+
+ it('overridePropsConfigure', () => {
+ Prototype.addGlobalPropsConfigure({
+ name: 'globalInsertProp1',
+ title: 'globalInsertPropTitle',
+ position: 'top',
+ });
+ const proto1 = new Prototype(divPrototypeConfig);
+ expect(proto1.getConfigure()[0].items).toHaveLength(3);
+ expect(proto1.getConfigure()[0].items[0].name).toBe('globalInsertProp1');
+ expect(proto1.getConfigure()[0].items[0].title).toBe('globalInsertPropTitle');
+
+ Prototype.overridePropsConfigure('Div', [
+ {
+ name: 'globalInsertProp1',
+ title: 'globalInsertPropTitleChanged',
+ },
+ ]);
+ const proto2 = new Prototype(divPrototypeConfig);
+ expect(proto2.getConfigure()[0].name).toBe('globalInsertProp1');
+ expect(proto2.getConfigure()[0].title).toBe('globalInsertPropTitleChanged');
+
+ Prototype.overridePropsConfigure('Div', {
+ globalInsertProp1: {
+ name: 'globalInsertProp1',
+ title: 'globalInsertPropTitleChanged new',
+ position: 'top',
+ },
+ });
+ const proto3 = new Prototype(divPrototypeConfig);
+ expect(proto3.getConfigure()[0].items[0].name).toBe('globalInsertProp1');
+ expect(proto3.getConfigure()[0].items[0].title).toBe('globalInsertPropTitleChanged new');
+ });
+
+ it('addGlobalExtraActions', () => {
+ function haha() { return 'heihei'; }
+ Prototype.addGlobalExtraActions(haha);
+ const proto1 = new Prototype(divPrototypeConfig);
+ expect(proto1.meta.availableActions).toHaveLength(4);
+ expect(proto1.meta.availableActions[3].name).toBe('haha');
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/bundle/trunk.test.ts b/packages/editor-preset-vision/tests/bundle/trunk.test.ts
new file mode 100644
index 000000000..d4bca021f
--- /dev/null
+++ b/packages/editor-preset-vision/tests/bundle/trunk.test.ts
@@ -0,0 +1,111 @@
+import { Component } from 'react';
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import divPrototypeConfig from '../fixtures/prototype/div-vision';
+import Prototype from '../../src/bundle/prototype';
+import Bundle from '../../src/bundle/bundle';
+import trunk from '../../src/bundle/trunk';
+import lg from '@ali/vu-logger';
+
+const proto1 = new Prototype(divPrototypeConfig);
+const protoConfig2 = cloneDeep(divPrototypeConfig);
+set(protoConfig2, 'componentName', 'Div2');
+const proto2 = new Prototype(protoConfig2);
+
+const protoConfig3 = cloneDeep(divPrototypeConfig);
+set(protoConfig3, 'componentName', 'Div3');
+const proto3 = new Prototype(protoConfig3);
+
+const mockComponentPrototype = jest.fn();
+jest.mock('../../src/bundle/bundle', () => {
+ // return {
+ // mockComponentPrototype: jest.fn().mockImplementation(() => {
+ // return proto;
+ // }),
+ // }
+ // return jest.fn().mockImplementation(() => {
+ // return {playSoundFile: fakePlaySoundFile};
+ // });
+ return jest.fn().mockImplementation(() => {
+ return {
+ get: () => {},
+ getList: () => { return []; },
+ getFromMeta: () => {},
+ };
+ });
+});
+
+const mockError = jest.fn();
+jest.mock('@ali/vu-logger');
+lg.error = mockError;
+
+function wrap(name, thing) {
+ return {
+ name,
+ componentName: name,
+ category: '布局',
+ module: thing,
+ };
+}
+
+function getComponentProtos() {
+ return [
+ wrap('Div', proto1),
+ // wrap('Div2', proto2),
+ // wrap('Div3', proto3),
+ wrap('DivPortal', [proto2, proto3]),
+ ];
+}
+
+class Div extends Component {}
+Div.displayName = 'Div';
+class Div2 extends Component {}
+Div2.displayName = 'Div2';
+class Div3 extends Component {}
+Div3.displayName = 'Div3';
+
+function getComponentViews() {
+ return [
+ wrap('Div', Div),
+ // wrap('Div2', Div2),
+ // wrap('Div3', Div3),
+ wrap('DivPortal', [Div2, Div3]),
+ ];
+}
+
+describe('Trunk', () => {
+ it('构造函数', () => {
+ const warn = console.warn = jest.fn();
+ const trunkChangeHandler = jest.fn();
+ const off = trunk.onTrunkChange(trunkChangeHandler);
+ trunk.addBundle(new Bundle([proto1], [Div]));
+ trunk.addBundle(new Bundle([proto2], [Div2]));
+ expect(trunkChangeHandler).toHaveBeenCalledTimes(2);
+ off();
+ trunk.addBundle(new Bundle([proto3], [Div3]));
+ expect(trunkChangeHandler).toHaveBeenCalledTimes(2);
+ trunk.getList();
+ trunk.getPrototype('Div');
+ trunk.getPrototypeById('Div');
+ trunk.getPrototypeView('Div');
+ trunk.listByCategory();
+ expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBeUndefined;
+ expect(mockError).toHaveBeenCalled();
+ trunk.registerComponentPrototypeMocker({ mockPrototype: jest.fn().mockImplementation(() => { return proto3; }) });
+ expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBe(proto3);
+ const hahaSetter = () => 'haha';
+ trunk.registerSetter('haha', hahaSetter);
+ expect(trunk.getSetter('haha')).toBe(hahaSetter);
+ trunk.getRecents(5);
+ trunk.setPackages();
+ expect(warn).toHaveBeenCalledTimes(1);
+ trunk.beforeLoadBundle();
+ expect(warn).toHaveBeenCalledTimes(2);
+ trunk.afterLoadBundle();
+ expect(warn).toHaveBeenCalledTimes(3);
+ trunk.getBundle();
+ expect(warn).toHaveBeenCalledTimes(4);
+ expect(trunk.isReady()).toBeTruthy;
+ });
+});
diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts
new file mode 100644
index 000000000..a2b410494
--- /dev/null
+++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts
@@ -0,0 +1,259 @@
+export default {
+ componentName: 'Div',
+ title: '容器',
+ docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ devMode: 'procode',
+ tags: ['布局'],
+ configure: {
+ props: [
+ {
+ type: 'field',
+ name: 'behavior',
+ title: '默认状态',
+ extraProps: {
+ display: 'inline',
+ defaultValue: 'NORMAL',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: '__style__',
+ title: {
+ label: '样式设置',
+ tip: '点击 ? 查看样式设置器用法指南',
+ docUrl: 'https://lark.alipay.com/legao/help/design-tool-style',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {},
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ advanced: true,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'group',
+ name: 'groupkh97h5kc',
+ title: '高级',
+ extraProps: {
+ display: 'accordion',
+ },
+ items: [
+ {
+ type: 'field',
+ name: 'fieldId',
+ title: {
+ label: '唯一标识',
+ },
+ extraProps: {
+ display: 'block',
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: '请输入唯一标识',
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'useFieldIdAsDomId',
+ title: {
+ label: '将唯一标识用作 DOM ID',
+ },
+ extraProps: {
+ display: 'block',
+ defaultValue: false,
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {},
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'customClassName',
+ title: '自定义样式类',
+ extraProps: {
+ display: 'block',
+ defaultValue: '',
+ },
+ setter: {
+ componentName: 'MixedSetter',
+ props: {
+ setters: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: null,
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ 'VariableSetter',
+ ],
+ },
+ },
+ },
+ {
+ type: 'field',
+ name: 'events',
+ title: {
+ label: '动作设置',
+ tip: '点击 ? 查看如何设置组件的事件响应动作',
+ docUrl: 'https://lark.alipay.com/legao/legao/events-call',
+ },
+ extraProps: {
+ display: 'accordion',
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ events: [
+ {
+ name: 'onClick',
+ title: '当点击时',
+ initialValue:
+ "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
+ },
+ {
+ name: 'onMouseEnter',
+ title: '当鼠标进入时',
+ initialValue:
+ "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
+ },
+ {
+ name: 'onMouseLeave',
+ title: '当鼠标离开时',
+ initialValue:
+ "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
+ },
+ ],
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'field',
+ name: 'onClick',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseEnter',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ {
+ type: 'field',
+ name: 'onMouseLeave',
+ extraProps: {
+ defaultValue: {
+ ignored: true,
+ },
+ },
+ setter: 'I18nSetter',
+ },
+ ],
+ },
+ ],
+ component: {
+ isContainer: true,
+ nestingRule: {},
+ },
+ supports: {},
+ },
+ experimental: {
+ callbacks: {},
+ initials: [
+ {
+ name: 'behavior',
+ },
+ {
+ name: '__style__',
+ },
+ {
+ name: 'fieldId',
+ },
+ {
+ name: 'useFieldIdAsDomId',
+ },
+ {
+ name: 'customClassName',
+ },
+ {
+ name: 'events',
+ },
+ {
+ name: 'onClick',
+ },
+ {
+ name: 'onMouseEnter',
+ },
+ {
+ name: 'onMouseLeave',
+ },
+ ],
+ filters: [],
+ autoruns: [],
+ },
+};
diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts
new file mode 100644
index 000000000..756c37649
--- /dev/null
+++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts
@@ -0,0 +1,293 @@
+export default {
+ title: '容器',
+ componentName: 'Div',
+ docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ category: '布局',
+ isContainer: true,
+ canOperating: false,
+ extraActions: [],
+ canContain: 'Form',
+ canDropTo: 'Div',
+ canDropIn: 'Div',
+ canResizing: true,
+ canDraging: false,
+ context: {},
+ initialChildren() {},
+ didDropIn() {},
+ didDropOut() {},
+ subtreeModified() {},
+ onResize() {},
+ onResizeStart() {},
+ onResizeEnd() {},
+ canUseCondition: true,
+ canLoop: true,
+ snippets: [
+ {
+ screenshot: 'https://img.alicdn.com/tfs/TB1CHN3u4z1gK0jSZSgXXavwpXa-112-64.png',
+ label: '普通型',
+ schema: {
+ componentName: 'Div',
+ props: {},
+ },
+ },
+ ],
+ configure: [
+ {
+ name: 'myName',
+ title: '我的名字',
+ display: 'tab',
+ initialValue: 'NORMAL',
+ defaultValue: 'NORMAL',
+ collapsed: true,
+ supportVariable: true,
+ accessor(field, val) {},
+ mutator(field, val) {},
+ disabled() {
+ return true;
+ },
+ useVariableChange() {},
+ allowTextInput: true,
+ liveTextEditing: true,
+ setter: [
+ {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ ],
+ },
+ {
+ name: 'mySlotName',
+ slotName: 'mySlotName',
+ slotTitle: '我的 Slot 名字',
+ display: 'tab',
+ initialValue: 'NORMAL',
+ defaultValue: 'NORMAL',
+ collapsed: true,
+ supportVariable: true,
+ accessor(field, val) {},
+ mutator(field, val) {},
+ disabled() {
+ return true;
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'behavior',
+ title: '默认状态',
+ display: 'inline',
+ initialValue: 'NORMAL',
+ supportVariable: true,
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: '__style__',
+ title: '样式设置',
+ display: 'accordion',
+ collapsed: false,
+ initialValue: {},
+ tip: {
+ url: 'https://lark.alipay.com/legao/help/design-tool-style',
+ content: '点击 ? 查看样式设置器用法指南',
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ advanced: true,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'group',
+ title: '高级',
+ display: 'accordion',
+ items: [
+ {
+ name: 'fieldId',
+ title: '唯一标识',
+ display: 'block',
+ tip:
+ '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。',
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: '请输入唯一标识',
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'useFieldIdAsDomId',
+ title: '将唯一标识用作 DOM ID',
+ display: 'block',
+ tip:
+ '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用',
+ initialValue: false,
+ setter: {
+ key: null,
+ ref: null,
+ props: {},
+ _owner: null,
+ },
+ },
+ {
+ name: 'customClassName',
+ title: '自定义样式类',
+ display: 'block',
+ supportVariable: true,
+ initialValue: '',
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: null,
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'events',
+ title: '动作设置',
+ tip: {
+ url: 'https://lark.alipay.com/legao/legao/events-call',
+ content: '点击 ? 查看如何设置组件的事件响应动作',
+ },
+ display: 'accordion',
+ initialValue: {
+ ignored: true,
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ events: [
+ {
+ name: 'onClick',
+ title: '当点击时',
+ initialValue:
+ "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
+ },
+ {
+ name: 'onMouseEnter',
+ title: '当鼠标进入时',
+ initialValue:
+ "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
+ },
+ {
+ name: 'onMouseLeave',
+ title: '当鼠标离开时',
+ initialValue:
+ "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
+ },
+ ],
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'onClick',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ {
+ name: 'onMouseEnter',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ {
+ name: 'onMouseLeave',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts
new file mode 100644
index 000000000..c4ae4374f
--- /dev/null
+++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts
@@ -0,0 +1,175 @@
+export default {
+ title: '容器',
+ componentName: 'Div',
+ docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
+ category: '布局',
+ isContainer: true,
+ configure: [
+ {
+ name: 'behavior',
+ title: '默认状态',
+ display: 'inline',
+ initialValue: 'NORMAL',
+ supportVariable: true,
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ options: [
+ {
+ title: '普通',
+ value: 'NORMAL',
+ },
+ {
+ title: '隐藏',
+ value: 'HIDDEN',
+ },
+ ],
+ loose: false,
+ cancelable: false,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: '__style__',
+ title: '样式设置',
+ display: 'accordion',
+ collapsed: false,
+ initialValue: {},
+ tip: {
+ url: 'https://lark.alipay.com/legao/help/design-tool-style',
+ content: '点击 ? 查看样式设置器用法指南',
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ advanced: true,
+ },
+ _owner: null,
+ },
+ },
+ {
+ type: 'group',
+ title: '高级',
+ display: 'accordion',
+ items: [
+ {
+ name: 'fieldId',
+ title: '唯一标识',
+ display: 'block',
+ tip:
+ '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。',
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: '请输入唯一标识',
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'useFieldIdAsDomId',
+ title: '将唯一标识用作 DOM ID',
+ display: 'block',
+ tip:
+ '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用',
+ initialValue: false,
+ setter: {
+ key: null,
+ ref: null,
+ props: {},
+ _owner: null,
+ },
+ },
+ {
+ name: 'customClassName',
+ title: '自定义样式类',
+ display: 'block',
+ supportVariable: true,
+ initialValue: '',
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ placeholder: null,
+ multiline: false,
+ rows: 10,
+ required: false,
+ pattern: null,
+ maxLength: null,
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'events',
+ title: '动作设置',
+ tip: {
+ url: 'https://lark.alipay.com/legao/legao/events-call',
+ content: '点击 ? 查看如何设置组件的事件响应动作',
+ },
+ display: 'accordion',
+ initialValue: {
+ ignored: true,
+ },
+ setter: {
+ key: null,
+ ref: null,
+ props: {
+ events: [
+ {
+ name: 'onClick',
+ title: '当点击时',
+ initialValue:
+ "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
+ },
+ {
+ name: 'onMouseEnter',
+ title: '当鼠标进入时',
+ initialValue:
+ "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
+ },
+ {
+ name: 'onMouseLeave',
+ title: '当鼠标离开时',
+ initialValue:
+ "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
+ },
+ ],
+ },
+ _owner: null,
+ },
+ },
+ {
+ name: 'onClick',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ {
+ name: 'onMouseEnter',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ {
+ name: 'onMouseLeave',
+ display: 'none',
+ initialValue: {
+ ignored: true,
+ },
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/editor-preset-vision/tests/fixtures/schema/form.ts b/packages/editor-preset-vision/tests/fixtures/schema/form.ts
new file mode 100644
index 000000000..5492a9ffb
--- /dev/null
+++ b/packages/editor-preset-vision/tests/fixtures/schema/form.ts
@@ -0,0 +1,955 @@
+export default {
+ componentName: 'Page',
+ id: 'node_k1ow3cb9',
+ props: {
+ extensions: {
+ 启用页头: true,
+ },
+ pageStyle: {
+ backgroundColor: '#f2f3f5',
+ },
+ containerStyle: {},
+ className: 'page_kh05zf9c',
+ templateVersion: '1.0.0',
+ },
+ lifeCycles: {
+ constructor: {
+ type: 'js',
+ compiled:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ source:
+ "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
+ },
+ },
+ condition: true,
+ css:
+ 'body{background-color:#f2f3f5}.card_kh05zf9d {\n margin-bottom: 12px;\n}.card_kh05zf9e {\n margin-bottom: 12px;\n}.button_kh05zf9f {\n margin-right: 16px;\n width: 80px\n}.button_kh05zf9g {\n width: 80px;\n}.div_kh05zf9h {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ methods: {
+ __initMethods__: {
+ type: 'js',
+ source: 'function (exports, module) { /*set actions code here*/ }',
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
+ },
+ },
+ dataSource: {
+ offline: [],
+ globalConfig: {
+ fit: {
+ compiled: '',
+ source: '',
+ type: 'js',
+ error: {},
+ },
+ },
+ online: [],
+ sync: true,
+ list: [],
+ },
+ children: [
+ {
+ componentName: 'RootHeader',
+ id: 'node_k1ow3cba',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'PageHeader',
+ id: 'node_k1ow3cbd',
+ props: {
+ extraContent: '',
+ __slot__extraContent: false,
+ __slot__action: false,
+ title: '',
+ content: '',
+ __slot__logo: false,
+ __slot__crumb: false,
+ crumb: '',
+ tab: '',
+ logo: '',
+ action: '',
+ __slot__tab: false,
+ __style__: {},
+ __slot__content: false,
+ fieldId: 'pageHeader_k1ow3h1i',
+ subTitle: '',
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'RootContent',
+ id: 'node_k1ow3cbb',
+ props: {
+ contentBgColor: 'transparent',
+ contentPadding: '0',
+ contentMargin: '20',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Form',
+ id: 'form',
+ props: {
+ size: 'medium',
+ labelAlign: 'top',
+ autoValidate: true,
+ scrollToFirstError: true,
+ autoUnmount: true,
+ behavior: 'NORMAL',
+ dataSource: {
+ type: 'variable',
+ variable: 'state.formData',
+ },
+ __style__: {},
+ fieldId: 'form',
+ fieldOptions: {},
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Card',
+ id: 'node_k1ow3cbj',
+ props: {
+ __slot__title: false,
+ subTitle: {
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ __slot__subTitle: false,
+ extra: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ className: 'card_kh05zf9d',
+ title: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '基本信息',
+ type: 'i18n',
+ },
+ __slot__extra: false,
+ showHeadDivider: true,
+ __style__: ':root {\n margin-bottom: 12px;\n}',
+ showTitleBullet: true,
+ contentHeight: '',
+ fieldId: 'card_k1ow3h1l',
+ dividerNoInset: false,
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'CardContent',
+ id: 'node_k1ow3cbk',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'ColumnsLayout',
+ id: 'node_k1ow3cbw',
+ props: {
+ layout: '4:8',
+ columnGap: '20',
+ rowGap: 0,
+ __style__: {},
+ fieldId: 'columns_k1ow3h1v',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cbx',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjm',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cbz',
+ props: {
+ fieldName: 'name',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [
+ {
+ type: 'required',
+ },
+ ],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1w',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '姓名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc1',
+ props: {
+ fieldName: 'englishName',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1y',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '英文名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc3',
+ props: {
+ fieldName: 'jobTitle',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h20',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '职位',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cby',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjn',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc2',
+ props: {
+ fieldName: 'nickName',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h1z',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '花名',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ {
+ componentName: 'SelectField',
+ id: 'node_k1ow3cc0',
+ props: {
+ fieldName: 'gender',
+ hasClear: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ mode: 'single',
+ showSearch: false,
+ autoWidth: true,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please select',
+ zh_CN: '请选择',
+ type: 'i18n',
+ },
+ hasBorder: true,
+ behavior: 'NORMAL',
+ value: '',
+ validation: [
+ {
+ type: 'required',
+ },
+ ],
+ __style__: {},
+ fieldId: 'select_k1ow3h1x',
+ notFoundContent: {
+ use: 'zh_CN',
+ type: 'i18n',
+ },
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'SelectField',
+ zh_CN: '性别',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ wrapperColOffset: 0,
+ hasSelectAll: false,
+ hasArrow: true,
+ size: 'medium',
+ labelAlign: 'top',
+ filterLocal: true,
+ dataSource: [
+ {
+ defaultChecked: false,
+ text: {
+ en_US: 'Option 1',
+ zh_CN: '男',
+ type: 'i18n',
+ __sid__: 'param_k1owc4tb',
+ },
+ __sid__: 'serial_k1owc4t1',
+ value: 'M',
+ sid: 'opt_k1owc4t2',
+ },
+ {
+ defaultChecked: false,
+ text: {
+ en_US: 'Option 2',
+ zh_CN: '女',
+ type: 'i18n',
+ __sid__: 'param_k1owc4tf',
+ },
+ __sid__: 'serial_k1owc4t2',
+ value: 'F',
+ sid: 'opt_k1owc4t3',
+ },
+ ],
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ useDetailValue: false,
+ searchDelay: 300,
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'Card',
+ id: 'node_k1ow3cbl',
+ props: {
+ __slot__title: false,
+ subTitle: {
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ __slot__subTitle: false,
+ extra: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ className: 'card_kh05zf9e',
+ title: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '部门信息',
+ type: 'i18n',
+ },
+ __slot__extra: false,
+ showHeadDivider: true,
+ __style__: ':root {\n margin-bottom: 12px;\n}',
+ showTitleBullet: true,
+ contentHeight: '',
+ fieldId: 'card_k1ow3h1m',
+ dividerNoInset: false,
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'CardContent',
+ id: 'node_k1ow3cbm',
+ props: {},
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc4',
+ props: {
+ fieldName: 'department',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h21',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '所属部门',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ {
+ componentName: 'ColumnsLayout',
+ id: 'node_k1ow3cc5',
+ props: {
+ layout: '6:6',
+ columnGap: '20',
+ rowGap: 0,
+ __style__: {},
+ fieldId: 'columns_k1ow3h22',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cc6',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjo',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc8',
+ props: {
+ fieldName: 'leader',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h23',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: '主管',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ ],
+ },
+ {
+ componentName: 'Column',
+ id: 'node_k1ow3cc7',
+ props: {
+ colSpan: '',
+ __style__: {},
+ fieldId: 'column_k1p1bnjp',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'TextField',
+ id: 'node_k1ow3cc9',
+ props: {
+ fieldName: 'hrg',
+ hasClear: false,
+ autoFocus: false,
+ tips: {
+ en_US: '',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ trim: false,
+ labelTextAlign: 'right',
+ placeholder: {
+ use: 'zh_CN',
+ en_US: 'please input',
+ zh_CN: '请输入',
+ type: 'i18n',
+ },
+ state: '',
+ behavior: 'NORMAL',
+ value: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ addonBefore: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ validation: [],
+ hasLimitHint: false,
+ cutString: false,
+ __style__: {},
+ fieldId: 'textField_k1ow3h24',
+ htmlType: 'input',
+ autoHeight: false,
+ labelColOffset: 0,
+ label: {
+ use: 'zh_CN',
+ en_US: 'TextField',
+ zh_CN: 'HRG',
+ type: 'i18n',
+ },
+ __category__: 'form',
+ labelColSpan: 4,
+ wrapperColSpan: 0,
+ rows: 4,
+ addonAfter: {
+ use: 'zh_CN',
+ zh_CN: '',
+ type: 'i18n',
+ },
+ wrapperColOffset: 0,
+ size: 'medium',
+ labelAlign: 'top',
+ __useMediator: 'value',
+ labelTipsTypes: 'none',
+ labelTipsIcon: '',
+ labelTipsText: {
+ type: 'i18n',
+ use: 'zh_CN',
+ en_US: '',
+ zh_CN: '',
+ },
+ maxLength: 200,
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'Div',
+ id: 'node_k1ow3cbo',
+ props: {
+ className: 'div_kh05zf9h',
+ behavior: 'NORMAL',
+ __style__:
+ ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
+ events: {},
+ fieldId: 'div_k1ow3h1o',
+ useFieldIdAsDomId: false,
+ customClassName: '',
+ },
+ condition: true,
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_k1ow3cbn',
+ props: {
+ triggerEventsWhenLoading: false,
+ onClick: {
+ rawType: 'events',
+ type: 'JSExpression',
+ value:
+ 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])',
+ events: [
+ {
+ name: 'submit',
+ id: 'submit',
+ params: {},
+ type: 'actionRef',
+ uuid: '1570966253282_0',
+ },
+ ],
+ },
+ size: 'medium',
+ baseIcon: '',
+ otherIcon: '',
+ className: 'button_kh05zf9f',
+ type: 'primary',
+ behavior: 'NORMAL',
+ loading: false,
+ content: {
+ use: 'zh_CN',
+ en_US: 'Button',
+ zh_CN: '提交',
+ type: 'i18n',
+ },
+ __style__: ':root {\n margin-right: 16px;\n width: 80px\n}',
+ fieldId: 'button_k1ow3h1n',
+ },
+ condition: true,
+ },
+ {
+ componentName: 'Button',
+ id: 'node_k1ow3cbp',
+ props: {
+ triggerEventsWhenLoading: false,
+ size: 'medium',
+ baseIcon: '',
+ otherIcon: '',
+ className: 'button_kh05zf9g',
+ type: 'normal',
+ behavior: 'NORMAL',
+ loading: false,
+ content: {
+ use: 'zh_CN',
+ en_US: 'Button',
+ zh_CN: '取消',
+ type: 'i18n',
+ },
+ __style__: ':root {\n width: 80px;\n}',
+ fieldId: 'button_k1ow3h1p',
+ },
+ condition: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'RootFooter',
+ id: 'node_k1ow3cbc',
+ props: {},
+ condition: true,
+ },
+ ],
+};
diff --git a/packages/editor-preset-vision/tests/fixtures/window.ts b/packages/editor-preset-vision/tests/fixtures/window.ts
new file mode 100644
index 000000000..b5886ad6d
--- /dev/null
+++ b/packages/editor-preset-vision/tests/fixtures/window.ts
@@ -0,0 +1,8 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+React.PropTypes = PropTypes;
+window.React = React;
+
+document.documentElement.requestFullscreen = () => {};
+document.exitFullscreen = () => {};
\ No newline at end of file
diff --git a/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap b/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap
new file mode 100644
index 000000000..0dd46763f
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap
@@ -0,0 +1,50 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`deepValueParser 测试 designMode: design 1`] = `
+Object {
+ "a": "111",
+ "arr": Array [
+ "111",
+ "111",
+ ],
+ "b": "222",
+ "c": "中文",
+ "slot": Object {
+ "type": "JSSlot",
+ "value": Array [
+ Object {
+ "componentName": "Div",
+ "props": Object {},
+ },
+ ],
+ },
+}
+`;
+
+exports[`deepValueParser 测试 designMode: live 1`] = `
+Object {
+ "a": Object {
+ "mock": "111",
+ "type": "JSExpression",
+ "value": "state.a",
+ },
+ "arr": Array [
+ Object {
+ "mock": "111",
+ "type": "JSExpression",
+ "value": "state.a",
+ },
+ Object {
+ "mock": "111",
+ "type": "JSExpression",
+ "value": "state.b",
+ },
+ ],
+ "b": Object {
+ "mock": "222",
+ "type": "JSExpression",
+ "value": "state.b",
+ },
+ "c": "中文",
+}
+`;
diff --git a/packages/editor-preset-vision/tests/master/bus.test.ts b/packages/editor-preset-vision/tests/master/bus.test.ts
new file mode 100644
index 000000000..9567e1726
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/bus.test.ts
@@ -0,0 +1,240 @@
+import '../fixtures/window';
+import bus from '../../src/bus';
+import { editor } from '../../src/editor';
+
+describe('bus 测试', () => {
+ afterEach(() => {
+ bus.unsub('evt1');
+ });
+ it('sub / pub 测试', () => {
+ const mockFn1 = jest.fn();
+ const off1 = bus.sub('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.pub('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ off1();
+
+ bus.pub('evt1', evtData);
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('on / emit 测试', () => {
+ const mockFn1 = jest.fn();
+ const off1 = bus.on('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ off1();
+
+ bus.emit('evt1', evtData);
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('once / emit 测试', () => {
+ const mockFn1 = jest.fn();
+ bus.once('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ bus.emit('evt1', evtData);
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('once / emit 测试,调用解绑函数', () => {
+ const mockFn1 = jest.fn();
+ const off1 = bus.once('evt1', mockFn1);
+
+ off1();
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).not.toHaveBeenCalled();
+ });
+
+ it('removeListener 测试', () => {
+ const mockFn1 = jest.fn();
+ bus.on('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ bus.removeListener('evt1', mockFn1);
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('unsub 测试 - 只有一个 handler', () => {
+ const mockFn1 = jest.fn();
+ bus.on('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ bus.unsub('evt1', mockFn1);
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('unsub 测试 - 只 unsub 一个 handler', () => {
+ const mockFn1 = jest.fn();
+ const mockFn2 = jest.fn();
+ bus.on('evt1', mockFn1);
+ bus.on('evt1', mockFn2);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+
+ bus.unsub('evt1', mockFn1);
+ const evtData2 = { a: 2 };
+ bus.emit('evt1', evtData2);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(2);
+ expect(mockFn2).toHaveBeenLastCalledWith(evtData2);
+ });
+
+ it('unsub 测试 - 多个 handler', () => {
+ const mockFn1 = jest.fn();
+ const mockFn2 = jest.fn();
+ bus.on('evt1', mockFn1);
+ bus.on('evt1', mockFn2);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+
+ bus.unsub('evt1');
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+ });
+
+ it('off 测试 - 只有一个 handler', () => {
+ const mockFn1 = jest.fn();
+ bus.on('evt1', mockFn1);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+
+ bus.off('evt1', mockFn1);
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ });
+
+ it('off 测试 - 只 off 一个 handler', () => {
+ const mockFn1 = jest.fn();
+ const mockFn2 = jest.fn();
+ bus.on('evt1', mockFn1);
+ bus.on('evt1', mockFn2);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+
+ bus.off('evt1', mockFn1);
+ const evtData2 = { a: 2 };
+ bus.emit('evt1', evtData2);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(2);
+ expect(mockFn2).toHaveBeenLastCalledWith(evtData2);
+ });
+
+ it('off 测试 - 多个 handler', () => {
+ const mockFn1 = jest.fn();
+ const mockFn2 = jest.fn();
+ bus.on('evt1', mockFn1);
+ bus.on('evt1', mockFn2);
+
+ const evtData = { a: 1 };
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+
+ bus.off('evt1');
+ bus.emit('evt1', evtData);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData);
+ expect(mockFn2).toHaveBeenCalledTimes(1);
+ expect(mockFn2).toHaveBeenCalledWith(evtData);
+ });
+
+ it('简单测试(dummy)', () => {
+ bus.getEmitter();
+ });
+
+ describe('editor 事件转发', () => {
+ const fwdEvtMap = {
+ 've.hotkey.callback.call': 'hotkey.callback.call',
+ 've.history.back': 'history.back',
+ 've.history.forward': 'history.forward',
+ 'node.prop.change': 'node.prop.change',
+ };
+
+ Object.keys(fwdEvtMap).forEach(veEventName => {
+ it(`${veEventName} 测试`, () => {
+ const mockFn1 = jest.fn();
+ const evtData1 = { a: 1 };
+ bus.on(veEventName, mockFn1);
+
+ editor.emit(fwdEvtMap[veEventName], evtData1);
+
+ expect(mockFn1).toHaveBeenCalledTimes(1);
+ expect(mockFn1).toHaveBeenCalledWith(evtData1);
+ });
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/context.test.ts b/packages/editor-preset-vision/tests/master/context.test.ts
new file mode 100644
index 000000000..362e4103d
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/context.test.ts
@@ -0,0 +1,62 @@
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Designer } from '../../src/designer/designer';
+import { VisualEngineContext } from '../../src/context';
+import { autorun } from '@ali/lowcode-editor-core';
+
+describe('VisualEngineContext 测试', () => {
+ it('registerManager | getManager', () => {
+ const ctx = new VisualEngineContext();
+
+ ctx.registerManager({
+ mgr1: {},
+ });
+ ctx.registerManager('mgr2', {});
+ expect(ctx.getManager('mgr1')).toEqual({});
+ });
+
+ it('registerModule | getModule', () => {
+ const ctx = new VisualEngineContext();
+
+ ctx.registerModule({
+ mod1: {},
+ });
+ ctx.registerModule('mod2', {});
+ expect(ctx.getModule('mod1')).toEqual({});
+ });
+
+ it('use | getPlugin', () => {
+ const ctx = new VisualEngineContext();
+
+ ctx.use('plugin1', { plugin: 1 });
+ ctx.registerManager({
+ mgr1: { manager: 1 },
+ });
+ ctx.registerModule({
+ mod1: { mod: 1 },
+ });
+ expect(ctx.getPlugin('plugin1')).toEqual({ plugin: 1 });
+ expect(ctx.getPlugin('mgr1')).toEqual({ manager: 1 });
+ expect(ctx.getPlugin('mod1')).toEqual({ mod: 1 });
+ expect(ctx.getPlugin()).toBeUndefined;
+
+ ctx.use('ve.settingField.variableSetter', {});
+ });
+
+ it('registerTreePane | getModule', () => {
+ const ctx = new VisualEngineContext();
+
+ ctx.registerTreePane({ pane: 1 }, { core: 2 });
+ expect(ctx.getModule('TreePane')).toEqual({ pane: 1 });
+ expect(ctx.getModule('TreeCore')).toEqual({ core: 2 });
+ });
+
+ it('registerDynamicSetterProvider', () => {
+ const ctx = new VisualEngineContext();
+
+ ctx.registerDynamicSetterProvider({});
+ expect(ctx.getPlugin('ve.plugin.setterProvider')).toEqual({});
+ ctx.registerDynamicSetterProvider();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts b/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts
new file mode 100644
index 000000000..8d2c27b80
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts
@@ -0,0 +1,84 @@
+import '../fixtures/window';
+import { deepValueParser } from '../../src/deep-value-parser';
+import { editor } from '../../src/editor';
+
+describe('deepValueParser 测试', () => {
+ it('null & undefined', () => {
+ expect(deepValueParser()).toBeNull;
+ expect(deepValueParser()).toBeUndefined;
+ });
+
+ it('designMode: design', () => {
+ expect(deepValueParser({
+ a: {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ b: {
+ type: 'JSExpression',
+ value: 'state.b',
+ mock: '222',
+ },
+ c: {
+ type: 'i18n',
+ use: 'zh_CN',
+ zh_CN: '中文',
+ en_US: 'eng',
+ },
+ slot: {
+ type: 'JSSlot',
+ value: [{
+ componentName: 'Div',
+ props: {},
+ }],
+ },
+ arr: [
+ {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ {
+ type: 'variable',
+ variable: 'state.b',
+ value: '111',
+ },
+ ],
+ })).toMatchSnapshot();
+ });
+
+ it('designMode: live', () => {
+ editor.set('designMode', 'live');
+ expect(deepValueParser({
+ a: {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ b: {
+ type: 'JSExpression',
+ value: 'state.b',
+ mock: '222',
+ },
+ c: {
+ type: 'i18n',
+ use: 'zh_CN',
+ zh_CN: '中文',
+ en_US: 'eng',
+ },
+ arr: [
+ {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ {
+ type: 'variable',
+ variable: 'state.b',
+ value: '111',
+ },
+ ],
+ })).toMatchSnapshot();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/drag-engine.test.ts b/packages/editor-preset-vision/tests/master/drag-engine.test.ts
new file mode 100644
index 000000000..438e30fac
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/drag-engine.test.ts
@@ -0,0 +1,182 @@
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Editor } from '@ali/lowcode-editor-core';
+// import { Designer } from '@ali/lowcode-designer';
+import { designer } from '../../src/editor';
+import DragEngine from '../../src/drag-engine';
+import formSchema from '../fixtures/schema/form';
+
+// const editor = new Editor();
+// const designer = new Designer({ editor });
+designer.project.open(formSchema);
+
+const mockBoostPrototype = jest.fn((e: MouseEvent) => {
+ return {
+ isPrototype: true,
+ getComponentName() {
+ return 'Div';
+ },
+ };
+});
+
+const mockBoostNode = jest.fn((e: MouseEvent) => {
+ return designer.currentDocument?.getNode('node_k1ow3cbo');
+});
+
+const mockBoostNodeData = jest.fn((e: MouseEvent) => {
+ return {
+ type: 'NodeData',
+ componentName: 'Div',
+ };
+});
+
+const mockBoostNull = jest.fn((e: MouseEvent) => {
+ return null;
+});
+
+const mockDragstart = jest.fn();
+const mockDrag = jest.fn();
+const mockDragend = jest.fn();
+
+describe('drag-engine 测试', () => {
+ it('prototype', async () => {
+ DragEngine.from(document, mockBoostPrototype);
+
+ DragEngine.onDragstart(mockDragstart);
+ DragEngine.onDrag(mockDrag);
+ DragEngine.onDragend(mockDragend);
+
+ const mousedownEvt = new MouseEvent('mousedown');
+ document.dispatchEvent(mousedownEvt);
+ designer.dragon.emitter.emit('dragstart', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ // await new Promise(resolve => resolve(setTimeout, 500));
+
+ expect(mockDragstart).toHaveBeenCalled();
+
+ designer.dragon.emitter.emit('drag', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDrag).toHaveBeenCalled();
+ expect(DragEngine.inDragging()).toBeTruthy;
+
+ designer.dragon.emitter.emit('dragend', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDragend).toHaveBeenCalled();
+ });
+
+ it('Node', async () => {
+ DragEngine.from(document, mockBoostNode);
+
+ DragEngine.onDragstart(mockDragstart);
+ DragEngine.onDrag(mockDrag);
+ DragEngine.onDragend(mockDragend);
+
+ const mousedownEvt = new MouseEvent('mousedown');
+ document.dispatchEvent(mousedownEvt);
+ designer.dragon.emitter.emit('dragstart', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ // await new Promise(resolve => resolve(setTimeout, 500));
+
+ expect(mockDragstart).toHaveBeenCalled();
+
+ designer.dragon.emitter.emit('drag', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDrag).toHaveBeenCalled();
+
+ designer.dragon.emitter.emit('dragend', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDragend).toHaveBeenCalled();
+ });
+
+ it('NodeData', async () => {
+ DragEngine.from(document, mockBoostNodeData);
+
+ DragEngine.onDragstart(mockDragstart);
+ DragEngine.onDrag(mockDrag);
+ DragEngine.onDragend(mockDragend);
+
+ const mousedownEvt = new MouseEvent('mousedown');
+ document.dispatchEvent(mousedownEvt);
+ designer.dragon.emitter.emit('dragstart', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ // await new Promise(resolve => resolve(setTimeout, 500));
+
+ expect(mockDragstart).toHaveBeenCalled();
+
+ designer.dragon.emitter.emit('drag', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDrag).toHaveBeenCalled();
+
+ designer.dragon.emitter.emit('dragend', {
+ dragObject: {
+ type: 'nodedata',
+ data: {
+ componentName: 'Div',
+ },
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDragend).toHaveBeenCalled();
+ });
+
+ it('null', async () => {
+ DragEngine.from(document, mockBoostNull);
+
+ DragEngine.onDragstart(mockDragstart);
+ DragEngine.onDrag(mockDrag);
+ DragEngine.onDragend(mockDragend);
+
+ const mousedownEvt = new MouseEvent('mousedown');
+ document.dispatchEvent(mousedownEvt);
+ designer.dragon.emitter.emit('dragstart', {
+ dragObject: {
+ nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
+ },
+ originalEvent: mousedownEvt,
+ });
+
+ expect(mockDragstart).toHaveBeenCalled();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/env.test.ts b/packages/editor-preset-vision/tests/master/env.test.ts
new file mode 100644
index 000000000..6f280295c
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/env.test.ts
@@ -0,0 +1,111 @@
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Designer } from '../../src/designer/designer';
+import env from '../../src/env';
+import { autorun } from '@ali/lowcode-editor-core';
+
+describe('env 测试', () => {
+ describe('常规 API 测试', () => {
+ it('setEnv / getEnv / setEnvMap / set / get', () => {
+ expect(env.getEnv('xxx')).toBeUndefined;
+
+ const mockFn1 = jest.fn();
+ const off1 = env.onEnvChange(mockFn1);
+
+ const envData = { a: 1 };
+ env.setEnv('xxx', envData);
+ expect(env.getEnv('xxx')).toEqual(envData);
+ expect(env.get('xxx')).toEqual(envData);
+ expect(mockFn1).toHaveBeenCalled();
+ expect(mockFn1).toHaveBeenCalledWith(env.envs, 'xxx', envData);
+ mockFn1.mockClear();
+
+ // 设置相同的值
+ env.setEnv('xxx', envData);
+ expect(env.getEnv('xxx')).toEqual(envData);
+ expect(env.get('xxx')).toEqual(envData);
+ expect(mockFn1).not.toHaveBeenCalled();
+ mockFn1.mockClear();
+
+ // 设置另一个 envName
+ const envData2 = { b: 1 };
+ env.set('yyy', envData2);
+ expect(env.getEnv('yyy')).toEqual(envData2);
+ expect(env.get('yyy')).toEqual(envData2);
+ expect(mockFn1).toHaveBeenCalled();
+ expect(mockFn1).toHaveBeenCalledWith(env.envs, 'yyy', envData2);
+ mockFn1.mockClear();
+
+ env.setEnvMap({
+ zzz: { a: 1, b: 1 },
+ });
+ expect(env.getEnv('xxx')).toBeUndefined;
+ expect(env.getEnv('yyy')).toBeUndefined;
+ expect(env.getEnv('zzz')).toEqual({ a: 1, b: 1 });
+ expect(mockFn1).toHaveBeenCalled();
+ expect(mockFn1).toHaveBeenCalledWith(env.envs);
+ mockFn1.mockClear();
+
+ // 解绑事件
+ off1();
+ env.setEnvMap({
+ zzz: { a: 1, b: 1 },
+ });
+ expect(mockFn1).not.toHaveBeenCalled();
+ mockFn1.mockClear();
+ });
+
+ it('setLocale / getLocale', () => {
+ expect(env.getLocale()).toBe('zh_CN');
+ env.setLocale('en_US');
+ expect(env.getLocale()).toBe('en_US');
+ });
+
+ it('setExpertMode / isExpertMode', () => {
+ expect(env.isExpertMode()).toBeFalsy;
+ env.setExpertMode('truthy value');
+ expect(env.isExpertMode()).toBeTruthy;
+ });
+
+ it('getSupportFeatures / setSupportFeatures / supports', () => {
+ expect(env.getSupportFeatures()).toEqual({});
+ env.setSupportFeatures({
+ mobile: true,
+ pc: true,
+ });
+ expect(env.getSupportFeatures()).toEqual({
+ mobile: true,
+ pc: true,
+ });
+ expect(env.supports('mobile')).toBeTruthy;
+ expect(env.supports('pc')).toBeTruthy;
+ expect(env.supports('iot')).toBeFalsy;
+ });
+
+ it('getAliSchemaVersion', () => {
+ expect(env.getAliSchemaVersion()).toBe('1.0.0');
+ });
+
+ it('envs obx 测试', async () => {
+ const mockFn = jest.fn();
+ env.clear();
+
+ autorun(() => {
+ mockFn(env.envs);
+ env.envs;
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 16));
+
+ expect(mockFn).toHaveBeenCalledTimes(1);
+ expect(mockFn).toHaveBeenLastCalledWith({});
+
+ env.setEnv('abc', { a: 1 });
+
+ await new Promise(resolve => setTimeout(resolve, 16));
+ expect(mockFn).toHaveBeenCalledTimes(2);
+ expect(mockFn).toHaveBeenLastCalledWith({ abc: { a: 1 } });
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/flags.test.ts b/packages/editor-preset-vision/tests/master/flags.test.ts
new file mode 100644
index 000000000..c5c3301e6
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/flags.test.ts
@@ -0,0 +1,71 @@
+import '../fixtures/window';
+import flagsCtrl from '../../src/flags';
+import domready from 'domready';
+
+jest.mock('domready', () => {
+ return (fn) => fn();
+});
+// domready.mockImplementation((fn) => fn());
+
+describe('flags 测试', () => {
+ it('flags', () => {
+ const mockFlagsChange = jest.fn();
+ flagsCtrl.flags = [];
+ const off = flagsCtrl.onFlagsChange(mockFlagsChange);
+ flagsCtrl.add('a');
+ expect(mockFlagsChange).toHaveBeenCalledTimes(1);
+ off();
+ flagsCtrl.add('b');
+ expect(mockFlagsChange).toHaveBeenCalledTimes(1);
+
+
+ expect(flagsCtrl.getFlags()).toEqual(['a', 'b']);
+
+ flagsCtrl.flags = [];
+ flagsCtrl.setDragMode(true);
+ expect(flagsCtrl.getFlags()).toEqual(['drag-mode']);
+ flagsCtrl.setDragMode(false);
+ expect(flagsCtrl.getFlags()).toEqual([]);
+
+ flagsCtrl.setPreviewMode(true);
+ expect(flagsCtrl.getFlags()).toEqual(['preview-mode']);
+ flagsCtrl.setPreviewMode(false);
+ expect(flagsCtrl.getFlags()).toEqual(['design-mode']);
+
+ flagsCtrl.flags = [];
+ flagsCtrl.setHideSlate(true);
+ expect(flagsCtrl.getFlags()).toEqual(['hide-slate']);
+ flagsCtrl.setHideSlate(false);
+ expect(flagsCtrl.getFlags()).toEqual([]);
+
+ flagsCtrl.flags = [];
+ flagsCtrl.setSlateFixedMode(true);
+ expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']);
+ flagsCtrl.setHideSlate(true);
+ expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']);
+ flagsCtrl.setSlateFixedMode(false);
+ expect(flagsCtrl.getFlags()).toEqual([]);
+
+ flagsCtrl.flags = [];
+ flagsCtrl.setSlateFullMode(true);
+ expect(flagsCtrl.getFlags()).toEqual(['slate-full-screen']);
+ flagsCtrl.setSlateFullMode(false);
+ expect(flagsCtrl.getFlags()).toEqual([]);
+
+ expect([].slice.apply(document.documentElement.classList)).toEqual(flagsCtrl.getFlags());
+
+ flagsCtrl.flags = [];
+ // setWithShell
+ flagsCtrl.setWithShell('shellA');
+ expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']);
+ flagsCtrl.setWithShell('iPhone6');
+ expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']);
+
+ flagsCtrl.flags = [];
+ // setSimulator
+ flagsCtrl.setSimulator('simA');
+ expect(flagsCtrl.getFlags()).toEqual(['simulator-simA']);
+ flagsCtrl.setSimulator('simB');
+ expect(flagsCtrl.getFlags()).toEqual(['simulator-simB']);
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/panes.test.ts b/packages/editor-preset-vision/tests/master/panes.test.ts
new file mode 100644
index 000000000..1a36eb271
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/panes.test.ts
@@ -0,0 +1,176 @@
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Designer } from '../../src/designer/designer';
+import panes from '../../src/panes';
+import { autorun } from '@ali/lowcode-editor-core';
+
+describe('panes 测试', () => {
+ it('add: type dock | PanelDock', () => {
+ const mockDockShow = jest.fn();
+ const mockDockHide = jest.fn();
+ const { DockPane } = panes;
+ const offDockShow = DockPane.onDockShow(mockDockShow);
+ const offDockHide = DockPane.onDockHide(mockDockHide);
+
+ const pane1 = panes.add({
+ name: 'trunk',
+ type: 'dock',
+ width: 300,
+ description: '组件库',
+ contents: [
+ {
+ title: '普通组件',
+ tip: '普通组件',
+ content: () => 'haha',
+ },
+ ],
+ menu: '组件库',
+ defaultFixed: true,
+ });
+
+ const pane2 = panes.add({
+ name: 'trunk2',
+ type: 'dock',
+ width: 300,
+ description: '组件库',
+ contents: [
+ {
+ title: '普通组件',
+ tip: '普通组件',
+ content: () => 'haha',
+ },
+ ],
+ menu: '组件库',
+ defaultFixed: true,
+ });
+
+ const pane3 = panes.add({
+ name: 'trunk3',
+ type: 'dock',
+ isAction: true,
+ });
+
+ // DockPane.container.items.map(item => console.log(item.name))
+ // 2 trunks + 1 outline-pane
+ expect(DockPane.container.items.length).toBe(4);
+
+ DockPane.activeDock(pane1);
+ // expect(mockDockShow).toHaveBeenCalledTimes(1);
+ // expect(mockDockShow).toHaveBeenLastCalledWith(pane1);
+ expect(DockPane.container.items[1].visible).toBeTruthy;
+
+ DockPane.activeDock(pane2);
+ expect(DockPane.container.items[2].visible).toBeTruthy;
+ // expect(mockDockShow).toHaveBeenCalledTimes(2);
+ // expect(mockDockShow).toHaveBeenLastCalledWith(pane2);
+ // expect(mockDockHide).toHaveBeenCalledTimes(1);
+ // expect(mockDockHide).toHaveBeenLastCalledWith(pane1);
+
+ DockPane.activeDock();
+ DockPane.activeDock({ name: 'unexisting' });
+
+ offDockShow();
+ offDockHide();
+
+ // DockPane.activeDock(pane1);
+ // expect(mockDockShow).toHaveBeenCalledTimes(2);
+ // expect(mockDockHide).toHaveBeenCalledTimes(1);
+
+ expect(typeof DockPane.getDocks).toBe('function');
+ DockPane.getDocks();
+ expect(typeof DockPane.setFixed).toBe('function');
+ DockPane.setFixed();
+ });
+
+ it('add: type action', () => {
+ panes.add({
+ name: 'trunk',
+ type: 'action',
+ init() {},
+ destroy() {},
+ });
+
+ const { ActionPane } = panes;
+ expect(typeof ActionPane.getActions).toBe('function');
+ ActionPane.getActions();
+ expect(typeof ActionPane.setActions).toBe('function');
+ ActionPane.setActions();
+ expect(ActionPane.getActions()).toBe(ActionPane.actions);
+ });
+
+ it('add: type action - extraConfig', () => {
+ panes.add({
+ name: 'trunk',
+ type: 'action',
+ init() {},
+ destroy() {},
+ }, {});
+ });
+
+ it('add: type action - function', () => {
+ panes.add(() => ({
+ name: 'trunk',
+ type: 'action',
+ init() {},
+ destroy() {},
+ }));
+ });
+
+ it('add: type tab', () => {
+ panes.add({
+ name: 'trunk',
+ type: 'tab',
+ });
+ const { TabPane } = panes;
+ expect(typeof TabPane.setFloat).toBe('function');
+ TabPane.setFloat();
+ });
+
+ it('add: type stage', () => {
+ panes.add({
+ id: 'stage1',
+ type: 'stage',
+ });
+ panes.add({
+ type: 'stage',
+ });
+
+ const { Stages } = panes;
+ expect(typeof Stages.getStage).toBe('function');
+ Stages.getStage();
+ expect(typeof Stages.createStage).toBe('function');
+ Stages.createStage({
+ id: 'stage1',
+ type: 'stage',
+ });
+ Stages.createStage({
+ type: 'stage',
+ });
+ });
+
+ it('add: type stage - id', () => {
+ panes.add({
+ id: 'trunk',
+ name: 'trunk',
+ type: 'stage',
+ });
+ });
+
+ it('add: type widget', () => {
+ panes.add({
+ name: 'trunk',
+ type: 'widget',
+ });
+ });
+
+ it('add: type null', () => {
+ panes.add({
+ name: 'trunk',
+ });
+
+ const { toolbar } = panes;
+ expect(typeof toolbar.setContents).toBe('function');
+ toolbar.setContents();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/symbols.test.ts b/packages/editor-preset-vision/tests/master/symbols.test.ts
new file mode 100644
index 000000000..35463ad8a
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/symbols.test.ts
@@ -0,0 +1,15 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+// import { Node } from '../../src/document/node/node';
+// import { Designer } from '../../src/designer/designer';
+import symbols from '../../src/symbols';
+
+describe('symbols 测试', () => {
+ it('API', () => {
+ symbols.create('abc');
+ symbols.create('abc');
+ symbols.get('abc');
+ });
+});
diff --git a/packages/editor-preset-vision/tests/master/viewport.test.ts b/packages/editor-preset-vision/tests/master/viewport.test.ts
new file mode 100644
index 000000000..2fb61c05a
--- /dev/null
+++ b/packages/editor-preset-vision/tests/master/viewport.test.ts
@@ -0,0 +1,189 @@
+import '../fixtures/window';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { editor } from '../../src/editor';
+import { Viewport } from '../../src/viewport';
+import domready from 'domready';
+
+// const editor = globalContext.get(Editor);
+
+jest.mock('domready', () => {
+ return (fn) => fn();
+});
+
+// 貌似 jsdom 没有响应 fullscreen 变更事件,先这么 mock 吧
+const mockSetFullscreen = flag => { document.fullscreen = flag; };
+
+describe('viewport 测试', () => {
+ mockSetFullscreen(true);
+
+ it('getDevice / setDevice / getViewport / onDeviceChange / onViewportChange', async () => {
+ const viewport = new Viewport();
+ const mockDeviceChange = jest.fn();
+ const mockViewportChange = jest.fn();
+ const offDevice = viewport.onDeviceChange(mockDeviceChange);
+ const offViewport = viewport.onViewportChange(mockViewportChange);
+ expect(viewport.getDevice()).toBe('pc');
+ expect(viewport.getViewport()).toBe('design-pc');
+ editor.set('currentDocument', { simulator: { set() {} } });
+
+ await viewport.setDevice('mobile');
+ expect(viewport.getDevice()).toBe('mobile');
+ expect(viewport.getViewport()).toBe('design-mobile');
+ expect(mockDeviceChange).toHaveBeenCalledTimes(1);
+ expect(mockViewportChange).toHaveBeenCalledTimes(1);
+
+ offDevice();
+ offViewport();
+ await viewport.setDevice('pc');
+ expect(mockDeviceChange).toHaveBeenCalledTimes(1);
+ expect(mockViewportChange).toHaveBeenCalledTimes(1);
+ });
+
+ it('setPreview / isPreview / togglePreivew / getViewport / onViewportChange', () => {
+ const viewport = new Viewport();
+ const mockViewportChange = jest.fn();
+ const mockPreivewChange = jest.fn();
+ const off = viewport.onViewportChange(mockViewportChange);
+ const offPreview = viewport.onPreview(mockPreivewChange);
+ viewport.setPreview(true);
+ expect(viewport.isPreview).toBeTruthy;
+ expect(viewport.getViewport()).toBe('preview-pc');
+ expect(mockViewportChange).toHaveBeenCalledTimes(1);
+ expect(mockPreivewChange).toHaveBeenCalledTimes(1);
+ viewport.setPreview(false);
+ expect(viewport.isPreview).toBeFalsy;
+ expect(viewport.getViewport()).toBe('design-pc');
+ expect(mockViewportChange).toHaveBeenCalledTimes(2);
+ expect(mockPreivewChange).toHaveBeenCalledTimes(2);
+ viewport.togglePreview();
+ expect(viewport.getViewport()).toBe('preview-pc');
+ expect(mockViewportChange).toHaveBeenCalledTimes(3);
+ expect(mockPreivewChange).toHaveBeenCalledTimes(3);
+ viewport.togglePreview();
+ expect(viewport.getViewport()).toBe('design-pc');
+ expect(mockViewportChange).toHaveBeenCalledTimes(4);
+ expect(mockPreivewChange).toHaveBeenCalledTimes(4);
+
+ off();
+ offPreview();
+ viewport.togglePreview();
+ expect(mockViewportChange).toHaveBeenCalledTimes(4);
+ expect(mockPreivewChange).toHaveBeenCalledTimes(4);
+ });
+
+ it('setFocusTarget / returnFocus / setFocus / isFocus / onFocusChange', () => {
+ const viewport = new Viewport();
+ const mockFocusChange = jest.fn();
+ const off = viewport.onFocusChange(mockFocusChange);
+ viewport.setFocusTarget(document.createElement('div'));
+ viewport.returnFocus();
+
+ viewport.setFocus(true);
+ expect(viewport.isFocus()).toBeTruthy();
+ expect(mockFocusChange).toHaveBeenCalledTimes(1);
+ expect(mockFocusChange).toHaveBeenLastCalledWith(true);
+ viewport.setFocus(false);
+ expect(viewport.isFocus()).toBeFalsy();
+ expect(mockFocusChange).toHaveBeenCalledTimes(2);
+ expect(mockFocusChange).toHaveBeenLastCalledWith(false);
+
+ off();
+ viewport.setFocus(false);
+ expect(mockFocusChange).toHaveBeenCalledTimes(2);
+ });
+
+ it('isFullscreen / toggleFullscreen / setFullscreen / onFullscreenChange', () => {
+ const viewport = new Viewport();
+ const mockFullscreenChange = jest.fn();
+ const off = viewport.onFullscreenChange(mockFullscreenChange);
+
+ mockSetFullscreen(false);
+ viewport.setFullscreen(true);
+ mockSetFullscreen(true);
+ expect(viewport.isFullscreen()).toBeTruthy;
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(1);
+ viewport.setFullscreen(true);
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(1);
+
+ mockSetFullscreen(true);
+ viewport.setFullscreen(false);
+ mockSetFullscreen(false);
+ expect(viewport.isFullscreen()).toBeFalsy;
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(2);
+ viewport.setFullscreen(false);
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(2);
+
+ mockSetFullscreen(true);
+ viewport.toggleFullscreen();
+ mockSetFullscreen(false);
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(3);
+ viewport.toggleFullscreen();
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(4);
+
+ off();
+ viewport.toggleFullscreen();
+ // expect(mockFullscreenChange).toHaveBeenCalledTimes(4);
+ });
+
+ it('setWithShell', () => {
+ const viewport = new Viewport();
+ viewport.setWithShell();
+ });
+
+ it('onSlateFixedChange', () => {
+ const viewport = new Viewport();
+ const mockSlateFixedChange = jest.fn();
+ const off = viewport.onSlateFixedChange(mockSlateFixedChange);
+
+ viewport.emitter.emit('slatefixed');
+ expect(mockSlateFixedChange).toHaveBeenCalledTimes(1);
+ off();
+ viewport.emitter.emit('slatefixed');
+ expect(mockSlateFixedChange).toHaveBeenCalledTimes(1);
+ });
+
+ it('setGlobalCSS', () => {
+ const viewport = new Viewport();
+ viewport.setGlobalCSS([{
+ media: '*',
+ type: 'URL',
+ content: '//path/to.css',
+ }, {
+ media: 'ALL',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }, {
+ media: '',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }, {
+ media: 'mobile',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }]);
+
+ viewport.cssResourceSet[0].apply();
+ viewport.cssResourceSet[0].init();
+ viewport.cssResourceSet[1].apply();
+ viewport.cssResourceSet[1].apply();
+ viewport.cssResourceSet[1].unmount();
+
+ viewport.setGlobalCSS([{
+ media: '*',
+ type: 'URL',
+ content: '//path/to.css',
+ }, {
+ media: 'ALL',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }, {
+ media: '',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }, {
+ media: 'mobile',
+ type: 'text',
+ content: 'body {font-size: 50px;}',
+ }]);
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts b/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts
new file mode 100644
index 000000000..ac7f3f45e
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts
@@ -0,0 +1,96 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor } from '@ali/lowcode-editor-core';
+import {
+ compatibleReducer,
+} from '../../src/props-reducers/downgrade-schema-reducer';
+import formSchema from '../fixtures/schema/form';
+
+describe('compatibleReducer 测试', () => {
+ it('compatibleReducer 测试', () => {
+ const downgradedProps = {
+ a: {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ props: {
+ slotTitle: '标题',
+ slotName: 'title',
+ },
+ children: [],
+ },
+ },
+ c: {
+ c1: {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ props: {
+ slotTitle: '标题',
+ slotName: 'title',
+ },
+ },
+ },
+ },
+ d: {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ e: {
+ e1: {
+ type: 'variable',
+ variable: 'state.b',
+ value: '222',
+ },
+ e2: {
+ type: 'JSExpression',
+ value: 'state.b',
+ mock: '222',
+ events: {},
+ },
+ },
+ };
+
+ expect(compatibleReducer({
+ a: {
+ type: 'JSSlot',
+ title: '标题',
+ name: 'title',
+ value: [],
+ },
+ c: {
+ c1: {
+ type: 'JSSlot',
+ title: '标题',
+ name: 'title',
+ value: undefined,
+ },
+ },
+ d: {
+ type: 'JSExpression',
+ value: 'state.a',
+ mock: '111',
+ },
+ e: {
+ e1: {
+ type: 'JSExpression',
+ value: 'state.b',
+ mock: '222',
+ },
+ e2: {
+ type: 'JSExpression',
+ value: 'state.b',
+ mock: '222',
+ events: {},
+ },
+ },
+ })).toEqual(downgradedProps);
+ });
+
+ it('空值', () => {
+ expect(compatibleReducer(null)).toBeNull;
+ expect(compatibleReducer(undefined)).toBeUndefined;
+ expect(compatibleReducer(111)).toBe(111);
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/filter.test.ts b/packages/editor-preset-vision/tests/props-reducers/filter.test.ts
new file mode 100644
index 000000000..691cfc086
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/filter.test.ts
@@ -0,0 +1,81 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor } from '@ali/lowcode-editor-core';
+import { filterReducer } from '../../src/props-reducers/filter-reducer';
+import formSchema from '../fixtures/schema/form';
+
+describe('filterReducer 测试', () => {
+ it('filterReducer 测试 - 有 filters', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ filters: [
+ {
+ name: 'shouldBeFitlered',
+ filter: () => false,
+ },
+ {
+ name: 'keeped',
+ filter: () => true,
+ },
+ {
+ name: 'throwErr',
+ filter: () => { throw new Error('xxx'); },
+ },
+ {
+ name: 'zzz',
+ filter: () => true,
+ },
+ ],
+ },
+ };
+ },
+ },
+ settingEntry: {
+ getProp(propName) {
+ return { name: propName };
+ },
+ },
+ };
+ expect(filterReducer({
+ shouldBeFitlered: 111,
+ keeped: 222,
+ noCorresponingFilter: 222,
+ throwErr: 111,
+ }, mockNode)).toEqual({
+ keeped: 222,
+ noCorresponingFilter: 222,
+ throwErr: 111,
+ });
+ });
+
+ it('filterReducer 测试 - 无 filters', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ filters: [],
+ },
+ };
+ },
+ },
+ settingEntry: {
+ getProp(propName) {
+ return { name: propName };
+ },
+ },
+ };
+ expect(filterReducer({
+ shouldBeFitlered: 111,
+ keeped: 222,
+ noCorresponingFilter: 222,
+ }, mockNode)).toEqual({
+ shouldBeFitlered: 111,
+ keeped: 222,
+ noCorresponingFilter: 222,
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts b/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts
new file mode 100644
index 000000000..874576a70
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts
@@ -0,0 +1,488 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { initNodeReducer } from '../../src/props-reducers/init-node-reducer';
+import formSchema from '../fixtures/schema/form';
+
+describe('initNodeReducer 测试', () => {
+ it('initNodeReducer 测试 - 有 initials', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [
+ {
+ name: 'propA',
+ initial: () => '111',
+ },
+ {
+ name: 'propB',
+ initial: () => '111',
+ },
+ {
+ name: 'propC',
+ initial: () => {
+ throw new Error('111');
+ },
+ },
+ {
+ name: 'propD',
+ initial: () => '111',
+ },
+ {
+ name: 'propE',
+ initial: () => '111',
+ },
+ {
+ name: 'propF',
+ initial: () => '111',
+ },
+ ],
+ },
+ };
+ },
+ prototype: {
+ options: {
+ configure: [
+ {
+ name: 'propF',
+ setter: {
+ type: {
+ displayName: 'I18nSetter',
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ settingEntry: {
+ getProp(propName) {
+ return { name: propName };
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+ expect(
+ initNodeReducer(
+ {
+ propA: '111',
+ propC: '222',
+ propD: {
+ type: 'JSExpression',
+ mock: '111',
+ },
+ propE: {
+ type: 'variable',
+ value: '111',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propA: '111',
+ propB: '111',
+ propC: '222',
+ propD: {
+ type: 'JSExpression',
+ mock: '111',
+ },
+ propE: {
+ type: 'variable',
+ value: '111',
+ },
+ propF: {
+ type: 'i18n',
+ use: 'zh_CN',
+ zh_CN: '111',
+ },
+ });
+ });
+
+ it('filterReducer 测试 - 无 initials', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {},
+ };
+ },
+ },
+ settingEntry: {
+ getProp(propName) {
+ return { name: propName };
+ },
+ },
+ };
+ expect(
+ initNodeReducer(
+ {
+ propA: 111,
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propA: 111,
+ });
+ });
+
+ describe('i18n', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [
+ {
+ name: 'propF',
+ initial: () => 111,
+ },
+ ],
+ },
+ };
+ },
+ },
+ prototype: {
+ options: {
+ configure: [
+ {
+ name: 'propF',
+ setter: {
+ type: {
+ displayName: 'I18nSetter',
+ },
+ },
+ },
+ ],
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+
+ it('isI18NObject(ov): true', () => {
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'i18n',
+ zh_CN: '222',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'i18n',
+ zh_CN: '222',
+ },
+ });
+ });
+
+ it('isJSExpression(ov): true', () => {
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'JSExpression',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'JSExpression',
+ value: 'state.a',
+ },
+ });
+ });
+
+ it('isJSBlock(ov): true', () => {
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'JSBlock',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'JSBlock',
+ value: 'state.a',
+ },
+ });
+ });
+
+ it('isJSSlot(ov): true', () => {
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'JSSlot',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'JSSlot',
+ value: 'state.a',
+ },
+ });
+ });
+
+ it('isVariable(ov): true', () => {
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ });
+ });
+
+ it('isI18NObject(v): false', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [
+ {
+ name: 'propF',
+ initial: () => 111,
+ },
+ ],
+ },
+ };
+ },
+ prototype: {
+ options: {
+ configure: [
+ {
+ name: 'propF',
+ setter: {
+ type: {
+ displayName: 'I18nSetter',
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ });
+ });
+
+ it('isI18NObject(v): false', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [{
+ name: 'propF',
+ initial: () => 111,
+ }],
+ },
+ };
+ },
+ },
+ prototype: {
+ options: {
+ configure: [
+ {
+ name: 'propF',
+ setter: {
+ type: {
+ displayName: 'I18nSetter',
+ },
+ },
+ },
+ ],
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+ expect(
+ initNodeReducer(
+ {
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'variable',
+ value: 'state.a',
+ },
+ });
+ });
+ });
+
+ it('成功使用兼容后的 i18n 对象', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [{
+ name: 'propF',
+ initial: () => {
+ return {
+ type: 'i18n',
+ use: 'zh_CN',
+ zh_CN: '111',
+ };
+ },
+ }],
+ },
+ };
+ },
+ prototype: {
+ options: {
+ configure: [
+ {
+ name: 'propF',
+ setter: {
+ type: {
+ displayName: 'I18nSetter',
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+ expect(
+ initNodeReducer(
+ {
+ propF: '111',
+ },
+ mockNode,
+ ),
+ ).toEqual({
+ propF: {
+ type: 'i18n',
+ use: 'zh_CN',
+ zh_CN: '111',
+ },
+ });
+ });
+
+ describe('fieldId', () => {
+ const mockNode = {
+ componentMeta: {
+ getMetadata() {
+ return {
+ experimental: {
+ initials: [
+ {
+ name: 'propA',
+ initial: () => '111',
+ },
+ ],
+ },
+ };
+ },
+ },
+ settingEntry: {
+ getProp(propName) {
+ return { name: propName };
+ },
+ },
+ props: {
+ has() {
+ return false;
+ },
+ add() {},
+ },
+ };
+ const editor = new Editor();
+ globalContext.register(editor, Editor);
+ const designer = new Designer({ editor });
+ editor.set('designer', designer);
+ designer.project.open(formSchema);
+ it('fieldId - 已存在', () => {
+ expect(initNodeReducer({
+ propA: '111',
+ fieldId: 'form',
+ }, mockNode)).toEqual({
+ propA: '111',
+ fieldId: undefined,
+ });
+ });
+
+ it('fieldId - 已存在,但有全局关闭标识', () => {
+ window.__disable_unique_id_checker__ = true;
+ expect(initNodeReducer({
+ propA: '111',
+ fieldId: 'form',
+ }, mockNode)).toEqual({
+ propA: '111',
+ fieldId: 'form',
+ });
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts b/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts
new file mode 100644
index 000000000..cd396da5e
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts
@@ -0,0 +1,78 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { liveLifecycleReducer } from '../../src/props-reducers/live-lifecycle-reducer';
+import formSchema from '../fixtures/schema/form';
+
+const editor = new Editor();
+globalContext.register(editor, Editor);
+
+it('liveLifecycleReducer 测试 - live', () => {
+ const mockDidMount = jest.fn();
+ const mockWillUnmount = jest.fn();
+ editor.set('designMode', 'live');
+ const newProps = liveLifecycleReducer(
+ {
+ lifeCycles: {
+ didMount: mockDidMount,
+ willUnmount: mockWillUnmount,
+ },
+ },
+ {
+ isRoot() {
+ return true;
+ },
+ },
+ );
+
+ const { lifeCycles } = newProps;
+ expect(typeof lifeCycles.componentDidMount).toBe('function');
+ expect(typeof lifeCycles.componentWillUnMount).toBe('function');
+
+ lifeCycles.didMount();
+ lifeCycles.willUnmount();
+
+ expect(mockDidMount).toHaveBeenCalled();
+ expect(mockWillUnmount).toHaveBeenCalled();
+});
+
+it('liveLifecycleReducer 测试 - design', () => {
+ const mockDidMount = jest.fn();
+ const mockWillUnmount = jest.fn();
+ editor.set('designMode', 'design');
+ const newProps = liveLifecycleReducer(
+ {
+ lifeCycles: {
+ didMount: mockDidMount,
+ willUnmount: mockWillUnmount,
+ },
+ },
+ {
+ isRoot() {
+ return true;
+ },
+ },
+ );
+
+ const { lifeCycles } = newProps;
+ expect(lifeCycles).toEqual({});
+});
+
+it('liveLifecycleReducer 测试', () => {
+ const mockDidMount = jest.fn();
+ const mockWillUnmount = jest.fn();
+ editor.set('designMode', 'design');
+ const newProps = liveLifecycleReducer(
+ {
+ propA: '111',
+ },
+ {
+ isRoot() {
+ return true;
+ },
+ },
+ );
+
+ const { lifeCycles } = newProps;
+ expect(lifeCycles).toBeUndefined;
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts b/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts
new file mode 100644
index 000000000..b1becca60
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts
@@ -0,0 +1,28 @@
+import '../fixtures/window';
+import { nodeTopFixedReducer } from '../../src/props-reducers/node-top-fixed-reducer';
+import formSchema from '../fixtures/schema/form';
+
+it('nodeTopFixedReducer 测试', () => {
+ expect(
+ nodeTopFixedReducer(
+ {
+ propA: '111',
+ },
+ { componentMeta: { isTopFixed: true } },
+ ),
+ ).toEqual({
+ propA: '111',
+ __isTopFixed__: true,
+ });
+
+ expect(
+ nodeTopFixedReducer(
+ {
+ propA: '111',
+ },
+ { componentMeta: { } },
+ ),
+ ).toEqual({
+ propA: '111',
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts b/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts
new file mode 100644
index 000000000..2e5fb8627
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts
@@ -0,0 +1,62 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { removeEmptyPropsReducer } from '../../src/props-reducers/remove-empty-prop-reducer';
+import formSchema from '../fixtures/schema/form';
+
+it('removeEmptyPropsReducer 测试', () => {
+ const newProps = removeEmptyPropsReducer(
+ {
+ propA: '111',
+ dataSource: {
+ online: [
+ {
+ options: {
+ params: [
+ {
+ name: 'propA',
+ value: '111',
+ },
+ {
+ value: '111',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ isRoot() {
+ return true;
+ },
+ },
+ );
+
+ expect(newProps).toEqual({
+ propA: '111',
+ dataSource: {
+ online: [
+ {
+ options: {
+ params: [{
+ name: 'propA',
+ value: '111',
+ }, {
+ value: '111',
+ }],
+ },
+ },
+ ],
+ list: [
+ {
+ options: {
+ params: {
+ propA: '111',
+ },
+ },
+ },
+ ],
+ },
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts b/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts
new file mode 100644
index 000000000..d79fbac75
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts
@@ -0,0 +1,121 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor, globalContext } from '@ali/lowcode-editor-core';
+import { stylePropsReducer } from '../../src/props-reducers/style-reducer';
+import formSchema from '../fixtures/schema/form';
+
+const editor: Editor = new Editor();
+globalContext.register(editor, Editor);
+
+beforeEach(() => {
+ // const designer = new Designer({ editor });
+ editor.set('designer', {
+ currentDocument: {
+ simulator: {
+ contentDocument: document,
+ },
+ },
+ });
+});
+// designer.project.open(formSchema);
+
+describe('stylePropsReducer 测试', () => {
+ it('无 style 相关属性', () => {
+ expect(stylePropsReducer({ propA: 1 })).toEqual({ propA: 1 });
+ });
+
+ it('__style__', () => {
+ const props = {
+ __style__: {
+ 'font-size': '50px',
+ },
+ };
+ const mockNode = { id: 'id1' };
+ expect(stylePropsReducer(props, mockNode)).toEqual({
+ className: '_css_pesudo_id1',
+ __style__: {
+ 'font-size': '50px',
+ },
+ });
+ expect(document.querySelector('#_style_pesudo_id1')).textContent =
+ '._css_pesudo_id1 { font-size: 50px; }';
+ });
+
+ it('__style__ - 无 contentDocument', () => {
+ editor.set('designer', {
+ currentDocument: {
+ simulator: {
+ contentDocument: undefined,
+ },
+ },
+ });
+ const props = {
+ __style__: {
+ 'font-size': '50px',
+ },
+ };
+ const mockNode = { id: 'id11' };
+ expect(stylePropsReducer(props, mockNode)).toEqual({
+ __style__: {
+ 'font-size': '50px',
+ },
+ });
+ expect(document.querySelector('#_style_pesudo_id11')).toBeNull;
+ });
+
+ it('__style__ - css id 已存在', () => {
+ const s = document.createElement('style');
+ s.setAttribute('type', 'text/css');
+ s.setAttribute('id', '_style_pesudo_id2');
+ document.getElementsByTagName('head')[0].appendChild(s);
+ s.appendChild(document.createTextNode('body {}'));
+ const props = {
+ __style__: {
+ 'font-size': '50px',
+ },
+ };
+ const mockNode = { id: 'id2' };
+ expect(stylePropsReducer(props, mockNode)).toEqual({
+ className: '_css_pesudo_id2',
+ __style__: {
+ 'font-size': '50px',
+ },
+ });
+ expect(document.querySelector('#_style_pesudo_id2')).textContent =
+ '._css_pesudo_id2 { font-size: 50px; }';
+ });
+
+ it('containerStyle', () => {
+ const props = {
+ containerStyle: {
+ 'font-size': '50px',
+ },
+ };
+ const mockNode = { id: 'id3' };
+ expect(stylePropsReducer(props, mockNode)).toEqual({
+ className: '_css_pesudo_id3',
+ containerStyle: {
+ 'font-size': '50px',
+ },
+ });
+ expect(document.querySelector('#_style_pesudo_id3')).textContent =
+ '._css_pesudo_id3 { font-size: 50px; }';
+ });
+
+ it('pageStyle', () => {
+ const props = {
+ pageStyle: {
+ 'font-size': '50rpx',
+ },
+ };
+ const mockNode = { id: 'id4' };
+ expect(stylePropsReducer(props, mockNode)).toEqual({
+ className: 'engine-document',
+ pageStyle: {
+ 'font-size': '50rpx',
+ },
+ });
+ expect(document.querySelector('#_style_pesudo_id4')).textContent =
+ '._css_pesudo_id4 { font-size: 50px; }';
+ });
+});
diff --git a/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts b/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts
new file mode 100644
index 000000000..b16c9b127
--- /dev/null
+++ b/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts
@@ -0,0 +1,107 @@
+import '../fixtures/window';
+import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
+import { Editor } from '@ali/lowcode-editor-core';
+import {
+ upgradePropsReducer,
+ upgradePageLifeCyclesReducer,
+} from '../../src/props-reducers/upgrade-reducer';
+import formSchema from '../fixtures/schema/form';
+
+describe('upgradePropsReducer 测试', () => {
+ it('upgradePropsReducer 测试', () => {
+ const props = {
+ a: {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ props: {
+ slotTitle: '标题',
+ slotName: 'title',
+ },
+ children: [],
+ },
+ },
+ b: {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Div',
+ props: {},
+ },
+ },
+ c: {
+ c1: {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ props: {
+ slotTitle: '标题',
+ slotName: 'title',
+ },
+ },
+ },
+ },
+ d: {
+ type: 'variable',
+ variable: 'state.a',
+ value: '111',
+ },
+ __slot__haha: true,
+ };
+
+ expect(upgradePropsReducer(props)).toEqual({
+ a: {
+ type: 'JSSlot',
+ title: '标题',
+ name: 'title',
+ value: [],
+ },
+ b: {
+ componentName: 'Div',
+ props: {},
+ },
+ c: {
+ c1: {
+ type: 'JSSlot',
+ title: '标题',
+ name: 'title',
+ value: undefined,
+ },
+ },
+ d: {
+ type: 'JSExpression',
+ value: 'state.a',
+ mock: '111',
+ },
+ });
+ });
+
+ it('空值', () => {
+ expect(upgradePropsReducer(null)).toBeNull;
+ expect(upgradePropsReducer(undefined)).toBeUndefined;
+ });
+});
+
+const editor = new Editor();
+const designer = new Designer({ editor });
+designer.project.open(formSchema);
+
+it('upgradePageLifeCyclesReducer 测试', () => {
+ const rootNode = designer.currentDocument?.rootNode;
+ const mockDidMount = jest.fn();
+ const mockWillUnmount = jest.fn();
+ upgradePageLifeCyclesReducer({
+ didMount: mockDidMount,
+ willUnmount: mockWillUnmount,
+ }, rootNode);
+
+ const lifeCycles = rootNode?.getPropValue(getConvertedExtraKey('lifeCycles'));
+
+ expect(typeof lifeCycles.didMount).toBe('function');
+ expect(typeof lifeCycles.willUnmount).toBe('function');
+
+ lifeCycles.didMount();
+ lifeCycles.willUnmount();
+
+ expect(mockDidMount).toHaveBeenCalled();
+ expect(mockWillUnmount).toHaveBeenCalled();
+});
diff --git a/packages/editor-preset-vision/tests/utils/index.ts b/packages/editor-preset-vision/tests/utils/index.ts
new file mode 100644
index 000000000..70fce0af2
--- /dev/null
+++ b/packages/editor-preset-vision/tests/utils/index.ts
@@ -0,0 +1 @@
+export { getIdsFromSchema, getNodeFromSchemaById } from '@ali/lowcode-test-mate/es/utils';
diff --git a/packages/editor-preset-vision/tests/vision-api/api-export.test.ts b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts
new file mode 100644
index 000000000..8da131447
--- /dev/null
+++ b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts
@@ -0,0 +1,87 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+// import { Project } from '../../src/project/project';
+import formSchema from '../fixtures/schema/form';
+import VisualEngine, {
+ designer,
+ editor,
+ skeleton,
+ /**
+ * VE.Popup
+ */
+ Popup,
+ /**
+ * VE Utils
+ */
+ utils,
+ I18nUtil,
+ Hotkey,
+ Env,
+ monitor,
+ /* pub/sub 集线器 */
+ Bus,
+ /* 事件 */
+ EVENTS,
+ /* 修饰方法 */
+ HOOKS,
+ Exchange,
+ context,
+ /**
+ * VE.init
+ *
+ * Initialized the whole VisualEngine UI
+ */
+ init,
+ ui,
+ Panes,
+ modules,
+ Trunk,
+ Prototype,
+ Bundle,
+ Pages,
+ DragEngine,
+ Viewport,
+ Version,
+ Project,
+ logger,
+ Symbols,
+} from '../../src';
+import { Editor } from '@ali/lowcode-editor-core';
+
+describe('API 多种导出场景测试', () => {
+ it('window.VisualEngine 和 npm 导出 API 测试', () => {
+ expect(VisualEngine).toBe(window.VisualEngine);
+ });
+
+ it('npm 导出 API 对比测试', () => {
+ expect(VisualEngine.designer).toBe(designer);
+ expect(VisualEngine.editor).toBe(editor);
+ expect(VisualEngine.skeleton).toBe(skeleton);
+ expect(VisualEngine.Popup).toBe(Popup);
+ expect(VisualEngine.utils).toBe(utils);
+ expect(VisualEngine.I18nUtil).toBe(I18nUtil);
+ expect(VisualEngine.Hotkey).toBe(Hotkey);
+ expect(VisualEngine.Env).toBe(Env);
+ expect(VisualEngine.monitor).toBe(monitor);
+ expect(VisualEngine.Bus).toBe(Bus);
+ expect(VisualEngine.EVENTS).toBe(EVENTS);
+ expect(VisualEngine.HOOKS).toBe(HOOKS);
+ expect(VisualEngine.Exchange).toBe(Exchange);
+ expect(VisualEngine.context).toBe(context);
+ expect(VisualEngine.init).toBe(init);
+ expect(VisualEngine.ui).toBe(ui);
+ expect(VisualEngine.Panes).toBe(Panes);
+ expect(VisualEngine.modules).toBe(modules);
+ expect(VisualEngine.Trunk).toBe(Trunk);
+ expect(VisualEngine.Prototype).toBe(Prototype);
+ expect(VisualEngine.Bundle).toBe(Bundle);
+ expect(VisualEngine.DragEngine).toBe(DragEngine);
+ expect(VisualEngine.Pages).toBe(Pages);
+ expect(VisualEngine.Viewport).toBe(Viewport);
+ expect(VisualEngine.Version).toBe(Version);
+ expect(VisualEngine.Project).toBe(Project);
+ expect(VisualEngine.logger).toBe(logger);
+ expect(VisualEngine.Symbols).toBe(Symbols);
+ });
+});
\ No newline at end of file
diff --git a/packages/editor-preset-vision/tests/vision-api/exchange.test.ts b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts
new file mode 100644
index 000000000..16c93b313
--- /dev/null
+++ b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts
@@ -0,0 +1,23 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import formSchema from '../fixtures/schema/form';
+import VisualEngine from '../../src';
+
+describe('VisualEngine.Exchange 相关 API 测试', () => {
+ it('select / getSelected', () => {
+ const doc = VisualEngine.Pages.addPage(formSchema);
+ VisualEngine.Exchange.select(doc?.getNode('form'));
+ expect(VisualEngine.Exchange.getSelected()?.componentName).toBe('Form');
+ expect(VisualEngine.Exchange.getSelected()?.id).toBe('form');
+
+ // clear selection
+ VisualEngine.Exchange.select();
+ expect(VisualEngine.Exchange.getSelected()).toBeUndefined;
+ });
+
+ it('onIntoView', () => {
+ expect(typeof VisualEngine.Exchange.onIntoView).toBe('function');
+ VisualEngine.Exchange.onIntoView();
+ });
+});
diff --git a/packages/editor-preset-vision/tests/vision-api/pages.test.ts b/packages/editor-preset-vision/tests/vision-api/pages.test.ts
new file mode 100644
index 000000000..87b04db0a
--- /dev/null
+++ b/packages/editor-preset-vision/tests/vision-api/pages.test.ts
@@ -0,0 +1,170 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import formSchema from '../fixtures/schema/form';
+import VisualEngine, { Prototype } from '../../src';
+import { Editor } from '@ali/lowcode-editor-core';
+import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
+import divPrototypeConfig from '../fixtures/prototype/div-vision';
+
+const pageSchema = { componentsTree: [formSchema] };
+
+describe('VisualEngine.Pages 相关 API 测试', () => {
+ afterEach(() => {
+ VisualEngine.Pages.unload();
+ });
+ describe('addPage 系列', () => {
+ it('基本的节点模型初始化,初始化传入 schema', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema)!;
+ expect(doc).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(doc.nodesMap.size).toBe(expectedNodeCnt);
+ });
+ it('基本的节点模型初始化,初始化传入 schema,带有 slot', () => {
+ const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title', {
+ type: 'JSBlock',
+ value: {
+ componentName: 'Slot',
+ children: [
+ {
+ componentName: 'Text',
+ id: 'node_k1ow3cbf',
+ props: {
+ showTitle: false,
+ behavior: 'NORMAL',
+ content: {
+ type: 'variable',
+ value: {
+ use: 'zh_CN',
+ en_US: 'Title',
+ zh_CN: '个人信息',
+ type: 'i18n',
+ },
+ variable: 'state.title',
+ },
+ __style__: {},
+ fieldId: 'text_k1ow3h1j',
+ maxLine: 0,
+ },
+ condition: true,
+ },
+ ],
+ props: {
+ slotTitle: '标题区域',
+ slotName: 'title',
+ },
+ },
+ });
+ const doc = VisualEngine.Pages.addPage({ componentsTree: [formSchemaWithSlot] })!;
+ expect(doc).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ // slot 会多出(1 + N)个节点
+ expect(doc.nodesMap.size).toBe(expectedNodeCnt + 2);
+ });
+ it('基本的节点模型初始化,初始化传入 schema,构造 prototype', () => {
+ const proto = new Prototype(divPrototypeConfig);
+ const doc = VisualEngine.Pages.addPage(pageSchema)!;
+ expect(doc).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ expect(doc.nodesMap.size).toBe(expectedNodeCnt);
+ });
+ it('导出 schema', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema)!;
+ expect(doc).toBeTruthy();
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const exportedData = doc.toData();
+ expect(exportedData).toHaveProperty('componentsMap');
+ expect(exportedData).toHaveProperty('componentsTree');
+ expect(exportedData.componentsTree).toHaveLength(1);
+ const exportedSchema = exportedData.componentsTree[0];
+ expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt);
+ });
+ });
+ describe('removePage 系列', () => {
+ it('removePage', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema)!;
+ expect(doc).toBeTruthy();
+ expect(VisualEngine.Pages.documents).toHaveLength(1);
+ VisualEngine.Pages.removePage(doc);
+ expect(VisualEngine.Pages.documents).toHaveLength(0);
+ });
+ });
+ describe('getPage 系列', () => {
+ it('getPage', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema);
+ const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page');
+ const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] });
+ expect(VisualEngine.Pages.getPage(0)).toBe(doc);
+ expect(VisualEngine.Pages.getPage((_doc) => _doc.rootNode.id === 'page')).toBe(doc2);
+ });
+ });
+ describe('setPages 系列', () => {
+ it('setPages componentsTree 只有一个元素', () => {
+ VisualEngine.Pages.setPages([pageSchema]);
+ const { currentDocument } = VisualEngine.Pages;
+ const ids = getIdsFromSchema(formSchema);
+ const expectedNodeCnt = ids.length;
+ const exportedData = currentDocument.toData();
+ expect(exportedData).toHaveProperty('componentsMap');
+ expect(exportedData).toHaveProperty('componentsTree');
+ expect(exportedData.componentsTree).toHaveLength(1);
+ const exportedSchema = exportedData.componentsTree[0];
+ expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt);
+ });
+ });
+ describe('setCurrentPage / getCurrentPage / currentPage / currentDocument 系列', () => {
+ it('getCurrentPage', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema)!;
+ expect(doc).toBeTruthy();
+ expect(doc).toBe(VisualEngine.Pages.getCurrentPage());
+ expect(doc).toBe(VisualEngine.Pages.currentDocument);
+ expect(doc).toBe(VisualEngine.Pages.currentPage);
+ });
+ it('setCurrentPage', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema);
+ expect(doc).toBe(VisualEngine.Pages.currentDocument);
+ const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page');
+ const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] });
+ expect(doc2).toBe(VisualEngine.Pages.currentDocument);
+ VisualEngine.Pages.setCurrentPage(doc);
+ expect(doc).toBe(VisualEngine.Pages.currentDocument);
+ });
+ });
+ describe('onCurrentPageChange 系列', () => {
+ it('多次切换', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema);
+ const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page');
+ const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] });
+ const docChangeHandler = jest.fn();
+ VisualEngine.Pages.onCurrentDocumentChange(docChangeHandler);
+ VisualEngine.Pages.setCurrentPage(doc);
+ expect(docChangeHandler).toHaveBeenCalledTimes(1);
+ expect(docChangeHandler).toHaveBeenLastCalledWith(doc);
+
+ VisualEngine.Pages.setCurrentPage(doc2);
+ expect(docChangeHandler).toHaveBeenCalledTimes(2);
+ expect(docChangeHandler).toHaveBeenLastCalledWith(doc2);
+ });
+ });
+ describe('toData 系列', () => {
+ it('基本的节点模型初始化,模型导出,初始化传入 schema', () => {
+ const doc = VisualEngine.Pages.addPage(pageSchema);
+ const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page');
+ const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] });
+ const dataList = VisualEngine.Pages.toData();
+ expect(dataList.length).toBe(2);
+ expect(dataList[0]).toHaveProperty('componentsMap');
+ expect(dataList[0]).toHaveProperty('componentsTree');
+ expect(dataList[0].componentsTree).toHaveLength(1);
+ expect(dataList[0].componentsTree[0].id).toBe('node_k1ow3cb9');
+ expect(dataList[1]).toHaveProperty('componentsMap');
+ expect(dataList[1]).toHaveProperty('componentsTree');
+ expect(dataList[1].componentsTree).toHaveLength(1);
+ expect(dataList[1].componentsTree[0].id).toBe('page');
+ });
+ });
+});
diff --git a/packages/editor-preset-vision/tests/vision-api/project.test.ts b/packages/editor-preset-vision/tests/vision-api/project.test.ts
new file mode 100644
index 000000000..bec071d51
--- /dev/null
+++ b/packages/editor-preset-vision/tests/vision-api/project.test.ts
@@ -0,0 +1,26 @@
+import set from 'lodash/set';
+import cloneDeep from 'lodash/clonedeep';
+import '../fixtures/window';
+import formSchema from '../fixtures/schema/form';
+import { Project } from '../../src';
+
+describe('VisualEngine.Project 相关 API 测试', () => {
+ it('getSchema / setSchema 系列', () => {
+ Project.setSchema({
+ componentsMap: {},
+ componentsTree: [formSchema],
+ });
+ expect(Project.getSchema()).toEqual({
+ componentsMap: {},
+ componentsTree: [formSchema],
+ });
+
+ });
+
+ it('setConfig', () => {
+ Project.setConfig({ haha: 1 });
+ expect(Project.get('config')).toEqual({
+ haha: 1,
+ });
+ });
+});
diff --git a/packages/editor-setters/CHANGELOG.md b/packages/editor-setters/CHANGELOG.md
new file mode 100644
index 000000000..3a70ca656
--- /dev/null
+++ b/packages/editor-setters/CHANGELOG.md
@@ -0,0 +1,856 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+
+## [0.13.1-29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-28...v0.13.1-29) (2020-12-03)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-28](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-27...v0.13.1-28) (2020-12-03)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-26...v0.13.1-27) (2020-12-02)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-25...v0.13.1-26) (2020-12-02)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-24...v0.13.1-25) (2020-12-01)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-23...v0.13.1-24) (2020-11-26)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-22...v0.13.1-23) (2020-11-25)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-19...v0.13.1-22) (2020-11-25)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-18...v0.13.1-19) (2020-11-24)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-15...v0.13.1-18) (2020-11-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-15) (2020-11-18)
+
+
+
+
+## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17)
+
+
+
+
+## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17)
+
+
+
+
+## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14)
+
+
+
+
+## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12)
+
+
+
+
+## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12)
+
+
+### Features
+
+* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6))
+
+
+
+
+## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10)
+
+
+
+
+## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28)
+
+
+
+
+## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28)
+
+
+
+
+## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27)
+
+
+
+
+## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27)
+
+
+
+
+## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27)
+
+
+
+
+## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27)
+
+
+
+
+## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27)
+
+
+
+
+
+## [0.13.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-12) (2020-11-18)
+
+
+
+
+## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17)
+
+
+
+
+## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17)
+
+
+
+
+## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14)
+
+
+
+
+## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12)
+
+
+
+
+## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12)
+
+
+### Features
+
+* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6))
+
+
+
+
+## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10)
+
+
+
+
+## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28)
+
+
+
+
+## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28)
+
+
+
+
+## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27)
+
+
+
+
+## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27)
+
+
+
+
+## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27)
+
+
+
+
+## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27)
+
+
+
+
+## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27)
+
+
+
+
+
+## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-9) (2020-10-26)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-8) (2020-10-26)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-6...v0.13.1-7) (2020-10-23)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-5...v0.13.1-6) (2020-10-22)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-4...v0.13.1-5) (2020-10-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-3...v0.13.1-4) (2020-10-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-2...v0.13.1-3) (2020-10-19)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.13.1-2) (2020-10-19)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.12.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-3) (2020-10-12)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.12.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-1...v0.12.1-2) (2020-09-23)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.12.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-9...v0.12.1-1) (2020-09-22)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.9-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-8...v1.0.9-9) (2020-09-22)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.9-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-7...v1.0.9-8) (2020-09-22)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.9-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-5...v1.0.9-7) (2020-09-18)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.9-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-2...v1.0.9-5) (2020-09-17)
+
+
+### Bug Fixes
+
+* source-editor bug & exp-setter bug ([5cd88d4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5cd88d4))
+
+
+
+
+
+## [1.0.9-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-1...v1.0.9-2) (2020-09-14)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.9-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-0...v1.0.9-1) (2020-09-14)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## 1.0.9-0 (2020-09-14)
+
+
+### Bug Fixes
+
+* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647))
+* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df))
+* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375))
+* rename MixinSetter to MixedSetter ([0e9a740](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e9a740))
+* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
+* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30))
+* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f))
+
+
+### Features
+
+* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0))
+* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84))
+* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807))
+* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f))
+* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6))
+* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7))
+
+
+
+
+
+## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.21...@ali/lowcode-editor-setters@1.0.8-0) (2020-09-09)
+
+
+### Bug Fixes
+
+* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
+* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30))
+* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f))
+* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647))
+* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df))
+* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375))
+
+
+### Features
+
+* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7))
+* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6))
+* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0))
+* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84))
+* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807))
+* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f))
+
+
+
+
+
+## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.6-0...@ali/lowcode-editor-setters@1.0.7-0) (2020-09-02)
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.19...@ali/lowcode-editor-setters@1.0.6-0) (2020-09-02)
+
+
+## [0.9.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.20...@ali/lowcode-editor-setters@0.9.21) (2020-09-03)
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.19...@ali/lowcode-editor-setters@0.9.20) (2020-09-03)
+
+
+
+### Bug Fixes
+
+* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
+* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30))
+* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f))
+* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647))
+* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df))
+* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375))
+
+
+### Features
+
+* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7))
+* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6))
+* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0))
+* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84))
+* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807))
+* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f))
+
+
+
+
+
+## [1.0.5-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.4-0...@ali/lowcode-editor-setters@1.0.5-0) (2020-08-20)
+
+
+## [0.9.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.18...@ali/lowcode-editor-setters@0.9.19) (2020-08-27)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.17...@ali/lowcode-editor-setters@0.9.18) (2020-08-24)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.16...@ali/lowcode-editor-setters@0.9.17) (2020-08-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.4-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.3-0...@ali/lowcode-editor-setters@1.0.4-0) (2020-08-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.3-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.2-0...@ali/lowcode-editor-setters@1.0.3-0) (2020-08-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.2-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.1-0...@ali/lowcode-editor-setters@1.0.2-0) (2020-08-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [1.0.1-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.16...@ali/lowcode-editor-setters@1.0.1-0) (2020-08-20)
+
+
+### Bug Fixes
+
+* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df))
+
+
+### Features
+
+* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0))
+* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84))
+* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807))
+* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6))
+
+
+
+
+
+# [1.0.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.14.0...@ali/lowcode-editor-setters@1.0.0) (2020-08-17)
+
+## [0.9.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.15...@ali/lowcode-editor-setters@0.9.16) (2020-08-19)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+# [0.14.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.13.0...@ali/lowcode-editor-setters@0.14.0) (2020-08-17)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+# [0.13.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.11.0...@ali/lowcode-editor-setters@0.13.0) (2020-08-17)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+# [0.12.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.11.0...@ali/lowcode-editor-setters@0.12.0) (2020-08-17)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+# [0.11.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.10.0...@ali/lowcode-editor-setters@0.11.0) (2020-08-16)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+# [0.10.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.14...@ali/lowcode-editor-setters@0.10.0) (2020-08-14)
+
+
+### Features
+
+* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84))
+
+
+
+
+
+## [0.9.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.13...@ali/lowcode-editor-setters@0.9.14) (2020-08-04)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.11...@ali/lowcode-editor-setters@0.9.13) (2020-08-04)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.11...@ali/lowcode-editor-setters@0.9.12) (2020-08-04)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.10...@ali/lowcode-editor-setters@0.9.11) (2020-07-28)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.9...@ali/lowcode-editor-setters@0.9.10) (2020-07-22)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.8...@ali/lowcode-editor-setters@0.9.9) (2020-07-21)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.7...@ali/lowcode-editor-setters@0.9.8) (2020-07-21)
+
+
+### Bug Fixes
+
+* rename MixinSetter to MixedSetter ([0e9a740](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e9a740))
+
+
+
+
+
+## [0.9.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.6...@ali/lowcode-editor-setters@0.9.7) (2020-07-13)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.5...@ali/lowcode-editor-setters@0.9.6) (2020-07-12)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.4...@ali/lowcode-editor-setters@0.9.5) (2020-06-23)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.3...@ali/lowcode-editor-setters@0.9.4) (2020-06-23)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.2...@ali/lowcode-editor-setters@0.9.3) (2020-06-15)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.9.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.1...@ali/lowcode-editor-setters@0.9.2) (2020-05-20)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## 0.9.1 (2020-05-18)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-editor-setters
+
+
+## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.14...@ali/lowcode-setters@0.8.15) (2020-05-15)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.13...@ali/lowcode-setters@0.8.14) (2020-05-13)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.12...@ali/lowcode-setters@0.8.13) (2020-05-08)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.11...@ali/lowcode-setters@0.8.12) (2020-05-07)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.10...@ali/lowcode-setters@0.8.11) (2020-04-27)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.9...@ali/lowcode-setters@0.8.10) (2020-04-27)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.8...@ali/lowcode-setters@0.8.9) (2020-04-27)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.7...@ali/lowcode-setters@0.8.8) (2020-04-16)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.6...@ali/lowcode-setters@0.8.7) (2020-04-15)
+
+
+### Features
+
+* mixin-setter get all setter ([eaa84d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eaa84d2))
+* mixin-setter get all setter ([a5eb62d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5eb62d))
+
+
+
+
+
+## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.5...@ali/lowcode-setters@0.8.6) (2020-03-31)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.4...@ali/lowcode-setters@0.8.5) (2020-03-30)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.3...@ali/lowcode-setters@0.8.4) (2020-03-30)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.2...@ali/lowcode-setters@0.8.3) (2020-03-30)
+
+
+
+
+**Note:** Version bump only for package @ali/lowcode-setters
+
+
+## 0.8.2 (2020-03-30)
+
+
+### Bug Fixes
+
+* ts type ([1732e7d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1732e7d))
+
+
+
+
+
+## 0.8.1 (2020-03-30)
+
+
+### Bug Fixes
+
+* ts type ([1732e7d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1732e7d))
diff --git a/packages/editor-setters/package.json b/packages/editor-setters/package.json
new file mode 100644
index 000000000..f67b65903
--- /dev/null
+++ b/packages/editor-setters/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@ali/lowcode-editor-setters",
+ "version": "0.13.1-29",
+ "description": "Builtin setters for Ali lowCode engine",
+ "files": [
+ "es",
+ "lib"
+ ],
+ "main": "lib/index.js",
+ "module": "es/index.js",
+ "scripts": {
+ "build": "build-scripts build --skip-demo",
+ "test": "ava",
+ "test:snapshot": "ava --update-snapshots"
+ },
+ "dependencies": {
+ "@ali/iceluna-comp-expression": "^1.0.6",
+ "@ali/iceluna-comp-form": "^1.0.20",
+ "@ali/iceluna-comp-list": "^1.0.26",
+ "@ali/iceluna-comp-object-button": "^1.0.23",
+ "@ali/iceluna-comp-react-node": "^1.0.5",
+ "@ali/iceluna-sdk": "^1.0.5-beta.24",
+ "@ali/lc-style-setter": "^0.0.1",
+ "@ali/lowcode-editor-core": "^0.13.1-29",
+ "@alifd/next": "^1.19.16",
+ "acorn": "^6.4.1",
+ "classnames": "^2.2.6",
+ "intl-messageformat": "^9.3.1",
+ "js-beautify": "^1.13.0",
+ "qs": "^6.9.1",
+ "react": "^16",
+ "react-dom": "^16.7.0",
+ "react-monaco-editor": "0.40.0"
+ },
+ "devDependencies": {
+ "@alib/build-scripts": "^0.1.18",
+ "@types/classnames": "^2.2.7",
+ "@types/node": "^13.7.1",
+ "@types/react": "^16",
+ "@types/react-dom": "^16",
+ "build-plugin-component": "^0.2.10",
+ "build-plugin-fusion": "^0.1.0",
+ "build-plugin-moment-locales": "^0.1.0"
+ },
+ "ava": {
+ "compileEnhancements": false,
+ "snapshotDir": "test/fixtures/__snapshots__",
+ "extensions": [
+ "ts"
+ ],
+ "require": [
+ "ts-node/register"
+ ]
+ },
+ "publishConfig": {
+ "registry": "https://registry.npm.alibaba-inc.com"
+ }
+}
diff --git a/packages/editor-setters/src/color-setter/index.tsx b/packages/editor-setters/src/color-setter/index.tsx
new file mode 100644
index 000000000..2415e8935
--- /dev/null
+++ b/packages/editor-setters/src/color-setter/index.tsx
@@ -0,0 +1,97 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { SketchPicker } from 'react-color';
+import { Input, Balloon } from '@alife/next';
+import './index.scss';
+
+interface Color {
+ rgb: any;
+ onChange: () => void;
+}
+
+export interface PluginProps {
+ value: string;
+ onChange: any;
+}
+
+export default class ColorPickerView extends PureComponent {
+ static display = 'ColorPicker';
+
+ static propTypes = {
+ onChange: PropTypes.func,
+ value: PropTypes.string,
+ };
+
+ static defaultProps = {
+ onChange: () => {},
+ value: '',
+ };
+
+ constructor(props: Readonly<{ value: string; defaultValue: string }>) {
+ super(props);
+ this.state = {
+ value: props.value || props.defaultValue,
+ };
+ }
+
+ static getDerivedStateFromProps(props: { value: string }, state: { preValue: string }) {
+ if (props.value != state.preValue) {
+ return {
+ preValue: props.value,
+ value: props.value,
+ };
+ }
+ return null;
+ }
+
+ onChangeComplete = (color: Color): void => {
+ let value;
+ if (color.rgb.a < 1) {
+ const { rgb } = color;
+ const rgba = [rgb.r, rgb.g, rgb.b, rgb.a];
+ value = `rgba(${rgba.join(',')})`;
+ } else {
+ value = color.hex;
+ }
+ this.setState({
+ value,
+ });
+ this.props.onChange && this.props.onChange(value);
+ };
+
+ onInputChange = (value: string): void => {
+ if (/^[0-9a-zA-Z]{6}$/.test(value)) value = `#${ value}`;
+ this.setState({
+ value,
+ });
+ this.props.onChange && this.props.onChange(value);
+ };
+
+ render(): React.ReactNode {
+ const { value, onChange, ...restProps } = this.props;
+ const boxStyle = {
+ backgroundColor: this.state.value,
+ };
+ const triggerNode = (
+
+ );
+ const InnerBeforeNode = (
+
+
+
+ );
+ return (
+
+ );
+ }
+}
diff --git a/packages/editor-setters/src/events-setter/index.tsx b/packages/editor-setters/src/events-setter/index.tsx
new file mode 100644
index 000000000..b0d806ecb
--- /dev/null
+++ b/packages/editor-setters/src/events-setter/index.tsx
@@ -0,0 +1,464 @@
+import { Component } from 'react';
+import { Radio, Menu, Table, Icon } from '@alifd/next';
+import nativeEvents from './native-events';
+
+import './index.scss';
+
+const { Item, Group } = Menu;
+const RadioGroup = Radio.Group;
+
+const EVENT_CONTENTS = {
+ COMPONENT_EVENT: 'componentEvent',
+ NATIVE_EVENT: 'nativeEvent',
+ LIFE_CYCLE_EVENT: 'lifeCycleEvent',
+};
+
+const DEFINITION_EVENT_TYPE = {
+ EVENTS: 'events',
+ NATIVE_EVENTS: 'nativeEvents',
+ LIFE_CYCLE_EVENT: 'lifeCycleEvent',
+};
+
+const SETTER_NAME = 'event-setter';
+
+export default class EventsSetter extends Component<{
+ value: any[];
+ onChange: (eventList: any[]) => void;
+}> {
+ state = {
+ eventBtns: [],
+ eventList: [],
+ selectType: null,
+ nativeEventList: [],
+ lifeCycleEventList: [],
+ eventDataList: (this.props?.value?.eventDataList ? this.props.value.eventDataList : this.props?.value) || [],
+ };
+
+ // constructor (){
+ // super();
+ // debugger;
+ // // if (!this.props || !this.props.value){
+ // // this.setState({
+ // // eventDataList:[]
+ // // })
+ // // }
+ // }
+
+ // static getDerivedStateFromProps(nextProps, prevState) {
+ // debugger;
+ // // const { value } = nextProps;
+ // // debugger;
+ // // if (value !== prevState.eventDataList) {
+ // // return {
+ // // value,
+ // // };
+ // // }
+ // return null;
+ // }
+
+ private bindEventName: string;
+
+ componentDidMount() {
+ console.log(this.state.eventDataList);
+
+ const { editor } = this.props.field;
+ this.initEventBtns();
+ this.initEventList();
+ editor.on(`${SETTER_NAME}.bindEvent`, (relatedEventName, paramStr) => {
+ this.bindEvent(relatedEventName, paramStr);
+ });
+ }
+
+ /**
+ * 初始化事件按钮
+ */
+ initEventBtns() {
+ const { definition } = this.props;
+ let isRoot = false;
+ let isCustom = false;
+ let eventBtns = [];
+ definition.map(item => {
+ if (item.type === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT) {
+ isRoot = true;
+ }
+
+ if (item.type === DEFINITION_EVENT_TYPE.EVENTS) {
+ isCustom = true;
+ }
+
+ return item;
+ });
+
+ if (isRoot) {
+ eventBtns = [
+ {
+ value: EVENT_CONTENTS.LIFE_CYCLE_EVENT,
+ label: '生命周期',
+ },
+ ];
+ } else if (isCustom) {
+ eventBtns = [
+ {
+ value: EVENT_CONTENTS.COMPONENT_EVENT,
+ label: '组件自带事件',
+ },
+ ];
+ } else {
+ eventBtns = [
+ {
+ value: EVENT_CONTENTS.NATIVE_EVENT,
+ label: '原生事件',
+ },
+ ];
+ }
+
+ this.setState({
+ eventBtns,
+ });
+ }
+
+ initEventList() {
+ const { definition } = this.props;
+ let nativeEventList = [];
+ definition.map(item => {
+ if (item.type === DEFINITION_EVENT_TYPE.EVENTS) {
+ this.checkEventListStatus(item.list, DEFINITION_EVENT_TYPE.EVENTS);
+ this.setState({
+ eventList: item.list,
+ });
+ }
+
+ if (item.type === DEFINITION_EVENT_TYPE.NATIVE_EVENTS) {
+ this.checkEventListStatus(
+ item.list,
+ DEFINITION_EVENT_TYPE.NATIVE_EVENTS,
+ );
+ nativeEventList = item.list;
+ }
+
+ if (item.type === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT) {
+ this.checkEventListStatus(
+ item.list,
+ DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT,
+ );
+ this.setState({
+ lifeCycleEventList: item.list,
+ });
+ }
+
+ return item;
+ });
+
+ if (nativeEventList.length == 0) {
+ nativeEventList = nativeEvents;
+ this.setState({
+ nativeEventList,
+ });
+ }
+ }
+
+ checkEventListStatus = (eventList: any[], eventType: string) => {
+ const { eventDataList } = this.state;
+ if (
+ eventType === DEFINITION_EVENT_TYPE.EVENTS ||
+ eventType === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT
+ ) {
+ eventList.map(item => {
+ item.disabled = false;
+ eventDataList.map(eventDataItem => {
+ if (item.name === eventDataItem.name) {
+ item.disabled = true;
+ }
+
+ return eventDataItem;
+ });
+
+ return item;
+ });
+ } else if (eventType === DEFINITION_EVENT_TYPE.NATIVE_EVENTS) {
+ eventDataList.map(eventDataItem => {
+ eventList.map(item => {
+ item.eventList.map(eventItem => {
+ if (eventItem.name === eventDataItem.name) {
+ item.disabled = true;
+ } else {
+ item.disabled = false;
+ }
+ return eventItem;
+ });
+ return item;
+ });
+
+ return eventDataItem;
+ });
+ }
+ };
+
+ /**
+ * 渲染事件信息
+ */
+ renderEventInfoCell = (value, index, record) => {
+ let eventTagText = '';
+ if (record.type === EVENT_CONTENTS.NATIVE_EVENT) {
+ eventTagText = '原';
+ } else if (record.type === EVENT_CONTENTS.COMPONENT_EVENT) {
+ eventTagText = '组';
+ } else if (record.type === EVENT_CONTENTS.LIFE_CYCLE_EVENT) {
+ eventTagText = '生';
+ }
+ return (
+
+
+
{eventTagText}
+ {record.name}
+
+
+
+ this.onRelatedEventNameClick(record.relatedEventName)}>
+ {record.relatedEventName || ''}
+
+
+
+ );
+ };
+
+ /**
+ * 渲染事件操作项
+ */
+ renderEventOperateCell = (eventName: string) => {
+ return (
+
+ this.openDialog(eventName)}
+ />
+ this.openDeleteEventDialog(eventName)}
+ />
+
+ );
+ };
+
+ updateEventListStatus = (eventName: string, unDisabled: boolean) => {
+ const { eventList, nativeEventList, lifeCycleEventList } = this.state;
+ eventList.map(item => {
+ if (item.name === eventName) {
+ item.disabled = !unDisabled;
+ }
+ return item;
+ });
+
+ lifeCycleEventList.map(item => {
+ if (item.name === eventName) {
+ item.disabled = !unDisabled;
+ }
+ return item;
+ });
+
+ nativeEventList.map(item => {
+ item.eventList.map(itemData => {
+ if (itemData.name === eventName) {
+ itemData.disabled = !unDisabled;
+ }
+ return itemData;
+ });
+
+ return item;
+ });
+ };
+
+ onRadioChange = value => {
+ this.setState({
+ selectType: value,
+ });
+ };
+
+
+ onEventMenuClick = (eventName: string) => {
+ const { selectType, eventDataList } = this.state;
+ eventDataList.push({
+ type: selectType,
+ name: eventName,
+ });
+
+ this.setState({
+ eventDataList,
+ });
+
+ this.updateEventListStatus(eventName);
+ this.closeEventMenu();
+ this.openDialog(eventName);
+ };
+
+ onRelatedEventNameClick = (eventName: string) => {
+ const { editor } = this.props.field;
+
+ editor.get('skeleton').getPanel('sourceEditor').show();
+
+ setTimeout(() => {
+ editor.emit('sourceEditor.focusByFunction', {
+ functionName: eventName,
+ });
+ }, 300);
+
+
+ // editor.emit('sourceEditor.focusByFunction',{
+ // functionName:eventName
+ // })
+ };
+
+ closeEventMenu = () => {
+ if (this.state.selectType !== null) {
+ this.setState({
+ selectType: null,
+ });
+ }
+ };
+
+ openDeleteEventDialog = (eventName: string) => {
+ this.deleteEvent(eventName);
+ // Dialog.confirm({
+ // title: '删除事件',
+ // content: '确定删除当前事件吗',
+ // onOk: () => this.deleteEvent(eventName),
+ // });
+ };
+
+ deleteEvent = (eventName: string) => {
+ const { eventDataList, eventList } = this.state;
+ eventDataList.map((item, index) => {
+ if (item.name === eventName) {
+ eventDataList.splice(index, 1);
+ }
+
+ return item;
+ });
+
+ this.setState({
+ eventDataList,
+ });
+ this.props.onChange({ eventDataList, eventList });
+ this.updateEventListStatus(eventName, true);
+ };
+
+ openDialog = (bindEventName: string) => {
+ const { editor } = this.props.field;
+ const { eventDataList } = this.state;
+ let paramStr;
+ eventDataList.map((item) => {
+ if (item.name == bindEventName) {
+ paramStr = item.paramStr;
+ }
+ return item;
+ });
+ this.bindEventName = bindEventName;
+ editor.emit('eventBindDialog.openDialog', bindEventName, SETTER_NAME, paramStr);
+ };
+
+
+ bindEvent = (relatedEventName: string, paramStr: string) => {
+ const { eventDataList, eventList } = this.state;
+ eventDataList.map(item => {
+ if (item.name === this.bindEventName) {
+ item.relatedEventName = relatedEventName;
+ if (paramStr) {
+ item.paramStr = paramStr;
+ }
+ }
+
+ return item;
+ });
+
+ this.setState({
+ eventDataList,
+ });
+
+
+ this.props.onChange({ eventDataList, eventList });
+
+ // this.closeDialog();
+ };
+
+ render() {
+ const {
+ eventBtns,
+ eventList,
+ nativeEventList,
+ lifeCycleEventList,
+ selectType,
+ eventDataList,
+ } = this.state;
+ const showEventList =
+ lifeCycleEventList.length > 0 ? lifeCycleEventList : eventList;
+ return (
+
+
+
+ {
+ eventBtns.length > 1 ? 点击选择事件类型 : 点击绑定事件
+ }
+
+
+
+ {selectType && selectType != EVENT_CONTENTS.NATIVE_EVENT && (
+
+ )}
+
+ {selectType && selectType === EVENT_CONTENTS.NATIVE_EVENT && (
+
+ )}
+
+
+
+ );
+ }
+}
diff --git a/packages/editor-setters/src/expression-setter/index.tsx b/packages/editor-setters/src/expression-setter/index.tsx
new file mode 100644
index 000000000..a4195a7a1
--- /dev/null
+++ b/packages/editor-setters/src/expression-setter/index.tsx
@@ -0,0 +1,349 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { Select, Balloon } from '@alife/next';
+import * as acorn from 'acorn';
+
+import { isJSExpression, generateI18n } from './locale/utils';
+import zhCN from './locale/zh-CN';
+
+import './index.scss';
+
+const { Option, AutoComplete } = Select;
+const { Tooltip } = Balloon;
+const helpMap = {
+ this: '容器上下文对象',
+ state: '容器的state',
+ props: '容器的props',
+ context: '容器的context',
+ schema: '页面上下文对象',
+ component: '组件上下文对象',
+ constants: '应用常量对象',
+ utils: '应用工具对象',
+ dataSourceMap: '容器数据源Map',
+ field: '表单Field对象',
+};
+
+export default class ExpressionView extends PureComponent {
+ static displayName = 'Expression';
+
+ static propTypes = {
+ context: PropTypes.object,
+ dataSource: PropTypes.array,
+ locale: PropTypes.string,
+ messages: PropTypes.object,
+ onChange: PropTypes.func,
+ placeholder: PropTypes.string,
+ value: PropTypes.string,
+ };
+
+ static defaultProps = {
+ context: {},
+ dataSource: [],
+ locale: 'zh-CN',
+ messages: zhCN,
+ onChange: () => {},
+ placeholder: '',
+ value: '',
+ };
+
+ expression: React.RefObject;
+
+ i18n: any;
+
+ t: void;
+
+ $input: any;
+
+ listenerFun: ((event: any) => void) | undefined;
+
+ static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) {
+ if (isJSExpression(val)) {
+ if (typeof val === 'object') {
+ return val.value;
+ } else if (typeof val === 'string') {
+ const arr = val.match(/^\{\{(.*?)\}\}$/);
+ if (arr) return arr[1];
+ }
+ }
+ return val;
+ }
+
+ constructor(props: any) {
+ super(props);
+ this.expression = React.createRef();
+ this.i18n = generateI18n(props.locale, props.messages);
+ this.state = {
+ value: ExpressionView.getInitValue(props.value),
+ dataSource: props.dataSource || [],
+ };
+ }
+
+ static getDerivedStateFromProps(props: { value: any; }, state: { preValue: any; }) {
+ const curValue = ExpressionView.getInitValue(props.value);
+ if (curValue !== state.preValue) {
+ return {
+ preValue: curValue,
+ value: curValue,
+ };
+ }
+ return null;
+ }
+
+ onChange(value: string, actionType: string) {
+ let realInputValue = value;
+ const realDataSource = null;
+ let nextCursorIndex: number;
+ // 更新值
+ if (actionType === 'itemClick' || actionType === 'enter') {
+ const curValue = this.state.value;
+ if (curValue) {
+ realInputValue = curValue + realInputValue;
+ }
+ }
+ // 更新数据源
+ const newState = {
+ value: realInputValue,
+ };
+ if (realDataSource !== null) newState.dataSource = realDataSource;
+ this.setState(newState, () => {
+ nextCursorIndex && this.setInputCursorPosition(nextCursorIndex);
+ });
+ // 默认加上变量表达式
+ this.t && clearTimeout(this.t);
+ this.t = setTimeout(() => {
+ const { onChange } = this.props;
+ // realInputValue = realInputValue ? `{{${realInputValue}}}` : undefined;
+ onChange && onChange({
+ type: 'JSExpression',
+ value: realInputValue,
+ });
+ }, 300);
+ }
+
+ /**
+ * 获取AutoComplete数据源
+ * @param {String}
+ * @return {Array}
+ */
+ getDataSource(): any[] {
+ const { editor } = this.props.field;
+ const schema = editor.get('designer').project.getSchema();
+ const stateMap = schema.componentsTree[0].state;
+ const dataSource = [];
+
+ for (const key in stateMap) {
+ dataSource.push(`this.state.${key}`);
+ }
+
+ return dataSource;
+ }
+
+ /**
+ * 获取光标前的对象字符串,语法解析获取对象字符串
+ * @param {String} str 模板字符串
+ * @return {String} 光标前的对象字符串
+ */
+ getCurrentFiled(str: string | any[]) {
+ str += 'x'; // .后面加一个x字符,便于acorn解析
+ try {
+ const astTree = acorn.parse(str);
+ const right = astTree.body[0].expression.right || astTree.body[0].expression;
+ if (right.type === 'MemberExpression') {
+ const { start, end } = right;
+ str = str.slice(start, end);
+ return { str, start, end };
+ }
+ } catch (e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取输入的上下文信息
+ * @param {Array}
+ * @return {Array}
+ */
+ getContextKeys(keys: []) {
+ const { editor } = this.props.field;
+ console.log(editor);
+ const limitKeys = ['schema', 'utils', 'constants'];
+ if (keys.length === 0) return limitKeys;
+ if (!limitKeys.includes(keys[0])) return [];
+ let result = [];
+ let keyValue = editor;
+ let assert = false;
+ keys.forEach(item => {
+ if (!keyValue[item] || typeof keyValue[item] !== 'object') {
+ assert = true;
+ }
+ if (keyValue[item]) {
+ keyValue = keyValue[item];
+ }
+ });
+ if (assert) return [];
+ result = Object.keys(keyValue);
+ return result;
+ // return utilsKeys.concat(constantsKeys).concat(schemaKeys);
+ }
+
+ /* 过滤key */
+ filterKey(obj: any, name: string) {
+ const filterKeys = [
+ 'reloadDataSource',
+ 'REACT_HOT_LOADER_RENDERED_GENERATION',
+ 'refs',
+ 'updater',
+ 'appHelper',
+ 'isReactComponent',
+ 'forceUpdate',
+ 'setState',
+ 'isPureReactComponent',
+ ];
+ const result = [];
+ for (const key in obj) {
+ if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) {
+ result.push(`${name}.${key}`);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 根据输入项进行筛选
+ * @param {String}
+ * @param {String}
+ * @return {Boolen}
+ */
+ filterOption(inputValue: string, item: { value: string | any[]; }) {
+ const cursorIndex = this.getInputCursorPosition();
+ const preStr = inputValue.substr(0, cursorIndex);
+ const lastKey: string[] = preStr.split('.').slice(-1);
+ if (!lastKey) return true;
+ if (item.value.indexOf(lastKey) > -1) return true;
+ return false;
+ }
+
+ // handleClick = () => {
+ // this.props.field.editor.emit('variableBindDialog.open');
+ // }
+
+ render() {
+ const { value, dataSource } = this.state;
+ const { placeholder } = this.props;
+ const isValObject = !!(value == '[object Object]');
+ const title = isValObject
+ ? this.i18n('valueIllegal')
+ : (value || placeholder || this.i18n('jsExpression')).toString();
+ const cursorIndex = this.getInputCursorPosition();
+ const childNode = cursorIndex ? (
+
+ {title.substr(0, cursorIndex)}
+ |
+ {title.substr(cursorIndex)}
+
+ ) : (
+ title
+ );
+
+ return (
+
+
+ {'{{'}}
+ innerAfter={{'}}'}}
+ popupClassName="expression-setter-item-inner"
+ // eslint-disable-next-line no-shadow
+ itemRender={({ value }) => {
+ return (
+
+ );
+ }}
+ onChange={this.onChange.bind(this)}
+ filter={this.filterOption.bind(this)}
+ />
+
+ )
+ }
+ >
+ {childNode}
+
+