feat: add some init codes

This commit is contained in:
1ncounter 2024-07-09 15:15:23 +08:00
parent 738f7af6de
commit d1702161c1
187 changed files with 1753 additions and 21414 deletions

View File

@ -30,10 +30,8 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@alilc/lowcode-core": "workspace:*", "@alilc/lowcode-engine-core": "workspace:*",
"@alilc/lowcode-shared": "workspace:*", "@alilc/lowcode-shared": "workspace:*",
"@alilc/lowcode-types": "workspace:*",
"@alilc/lowcode-utils": "workspace:*",
"@alifd/next": "^1.27.8", "@alifd/next": "^1.27.8",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"lodash-es": "^4.17.20", "lodash-es": "^4.17.20",
@ -55,10 +53,8 @@
}, },
"peerDependencies": { "peerDependencies": {
"@alifd/next": "^1.27.8", "@alifd/next": "^1.27.8",
"@alilc/lowcode-core": "workspace:*", "@alilc/lowcode-engine-core": "workspace:*",
"@alilc/lowcode-shared": "workspace:*", "@alilc/lowcode-shared": "workspace:*",
"@alilc/lowcode-types": "workspace:*",
"@alilc/lowcode-utils": "workspace:*",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },

View File

@ -1,8 +1,8 @@
import { signal, uniqueId, type Spec } from '@alilc/lowcode-shared'; import { signal, uniqueId, ComponentTreeRoot } from '@alilc/lowcode-shared';
import { type Project } from '../project'; import { type Project } from '../project';
import { History } from './history'; import { History } from './history';
export interface DocumentSchema extends Spec.ComponentTreeRoot { export interface DocumentSchema extends ComponentTreeRoot {
id: string; id: string;
} }

View File

@ -1,8 +1,8 @@
import { Spec } from '@alilc/lowcode-shared'; import { ComponentNode } from '@alilc/lowcode-shared';
import { type ComponentMeta } from '../component-meta'; import { type ComponentMeta } from '../component-meta';
import { type Prop } from './prop'; import { type Prop } from './prop';
export interface Node<Schema extends Spec.ComponentNode = Spec.ComponentNode> { export interface Node<Schema extends ComponentNode = ComponentNode> {
/** /**
* id * id
* node id * node id
@ -353,6 +353,6 @@ export interface Node<Schema extends Spec.ComponentNode = Spec.ComponentNode> {
}; };
} }
export function createNode<Schema extends Spec.ComponentNode>(nodeSchema: Schema): Node<Schema> { export function createNode<Schema extends ComponentNode>(nodeSchema: Schema): Node<Schema> {
return {}; return {};
} }

View File

@ -1,9 +0,0 @@
export class DocumentModel {
a = 1;
c = {};
constructor() {
const b = { x: { y: 2 } };
const c: number = 2;
this.a = b?.x?.y;
}
}

View File

@ -1,9 +0,0 @@
export class Node2 {
a = 1;
c = {};
constructor() {
const b = { x: { y: 2 } };
const c: number = 2;
this.a = b?.x?.y;
}
}

View File

@ -1,55 +0,0 @@
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 { configure: { advanced: 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);
});

View File

@ -1,72 +0,0 @@
import { Editor } from '@alilc/lowcode-editor-core';
import { IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { isPlainObject, isVariable, isJSBlock } from '@alilc/lowcode-utils';
import '../fixtures/window';
import { Designer } from '../../src/designer/designer';
import { DocumentModel } from '../../src/document/document-model';
import { Project } from '../../src/project/project';
import formSchema from '../fixtures/schema/form';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
/**
* bug
* Prop setValue dispose Prop Node dispose
* dispose variable JSExpression
*
* propsReducer Init / Upgrade props Node dispose Prop
*/
function upgradePropsReducer(props: any): 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;
}
describe('Node 方法测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
it('原始 prop 值是 variable 结构,通过一个 propsReducer 转成了 JSExpression 结构', () => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
designer.addPropsReducer(upgradePropsReducer, IPublicEnumTransformStage.Upgrade);
project = designer.project;
doc = new DocumentModel(project, formSchema);
const form = doc.getNode('form');
expect(form.getPropValue('dataSource')).toEqual({
type: 'JSExpression',
value: 'state.formData',
})
});
});

View File

@ -1,6 +0,0 @@
背景:
在 UT 的基础上,希望借助一些 Bug 修复来完成场景测试,从而进一步增强稳定性。
至少在真正的 E2E 测试来临之前,我们保证不会重复犯两次相同的错误。
做法:
Bugs 文件夹每个文件记录一个 bug 修复的场景测试~

View File

@ -1,127 +0,0 @@
import '../../fixtures/window';
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { DocumentModel } from '../../../src/document/document-model';
import { Designer } from '../../../src/designer/designer';
import DragResizeEngine from '../../../src/builtin-simulator/bem-tools/drag-resize-engine';
import formSchema from '../../fixtures/schema/form';
import { fireEvent, createEvent } from '@testing-library/react';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
describe('DragResizeEngine 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let resizeEngine: DragResizeEngine;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = project.createDocument(formSchema);
doc.open();
resizeEngine = new DragResizeEngine(designer);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer.purge();
resizeEngine = null;
designer = null;
project = null;
});
it('from', () => {
const resizeStartMockFn = jest.fn();
const resizeMockFn = jest.fn();
const resizeEndMockFn = jest.fn();
const offResizeStart = resizeEngine.onResizeStart(resizeStartMockFn);
const offResize = resizeEngine.onResize(resizeMockFn);
const offResizeEnd = resizeEngine.onResizeEnd(resizeEndMockFn);
const boostedNode = doc.getNode('node_k1ow3cbn');
const mockBoostFn = jest
.fn((e) => {
return boostedNode;
});
// do nothing
const noop = resizeEngine.from();
noop();
const offFrom = resizeEngine.from(document, 'e', mockBoostFn);
const mouseDownEvt = createEvent.mouseDown(document, { clientX: 100, clientY: 100 });
fireEvent(document, mouseDownEvt);
expect(resizeStartMockFn).toHaveBeenCalledTimes(1);
expect(resizeStartMockFn.mock.calls[0][0]).toBe(mouseDownEvt);
expect(resizeStartMockFn.mock.calls[0][1]).toBe('e');
expect(resizeStartMockFn.mock.calls[0][2]).toBe(boostedNode);
expect(resizeEngine.isDragResizing()).toBeTruthy();
const mouseMoveEvt1 = createEvent.mouseMove(document, { clientX: 108, clientY: 108 });
fireEvent(document, mouseMoveEvt1);
expect(resizeMockFn).toHaveBeenCalledTimes(1);
expect(resizeMockFn.mock.calls[0][0]).toBe(mouseMoveEvt1);
expect(resizeMockFn.mock.calls[0][1]).toBe('e');
expect(resizeMockFn.mock.calls[0][2]).toBe(boostedNode);
expect(resizeMockFn.mock.calls[0][3]).toBe(8);
expect(resizeMockFn.mock.calls[0][4]).toBe(8);
const mouseMoveEvt2 = createEvent.mouseMove(document, { clientX: 110, clientY: 110 }, 10, 10);
fireEvent(document, mouseMoveEvt2);
expect(resizeMockFn).toHaveBeenCalledTimes(2);
expect(resizeMockFn.mock.calls[1][0]).toBe(mouseMoveEvt2);
expect(resizeMockFn.mock.calls[1][1]).toBe('e');
expect(resizeMockFn.mock.calls[1][2]).toBe(boostedNode);
expect(resizeMockFn.mock.calls[1][3]).toBe(10);
expect(resizeMockFn.mock.calls[1][4]).toBe(10);
const mouseUpEvt = createEvent.mouseUp(document, { clientX: 118, clientY: 118 });
fireEvent(document, mouseUpEvt);
expect(resizeEndMockFn).toHaveBeenCalledTimes(1);
expect(resizeEndMockFn.mock.calls[0][0]).toBe(mouseUpEvt);
expect(resizeEndMockFn.mock.calls[0][1]).toBe('e');
expect(resizeEndMockFn.mock.calls[0][2]).toBe(boostedNode);
expect(resizeEngine.isDragResizing()).toBeFalsy();
offResizeStart();
offResize();
offResizeEnd();
resizeStartMockFn.mockClear();
resizeMockFn.mockClear();
fireEvent.mouseMove(document, { clientX: 100, clientY: 100 });
expect(resizeMockFn).not.toHaveBeenCalled();
offFrom();
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
expect(resizeStartMockFn).not.toHaveBeenCalled();
});
it('has sensor', () => {
const mockDoc = document.createElement('iframe').contentWindow?.document;
project.mountSimulator({
sensorAvailable: true,
contentDocument: document,
});
const mockBoostFn = jest
.fn((e) => {
return doc.getNode('node_k1ow3cbn');
});
const offFrom = resizeEngine.from(document, 'e', mockBoostFn);
// TODO: 想办法 mock 一个 iframe.currentDocument
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
});
});

View File

@ -1,50 +0,0 @@
import '../../fixtures/window';
import { Editor } from '@alilc/lowcode-editor-core';
import { Designer } from '../../../src/designer/designer';
import { BemToolsManager } from '../../../src/builtin-simulator/bem-tools/manager';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
describe('Node 方法测试', () => {
let editor: Editor;
let designer: Designer;
// let project: Project;
// let doc: DocumentModel;
let manager: BemToolsManager;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
// project = designer.project;
// doc = new DocumentModel(project, formSchema);
manager = new BemToolsManager(designer);
});
afterEach(() => {
// project.unload();
designer.purge();
editor = null;
designer = null;
// project = null;
});
it('addBemTools / removeBemTools / getAllBemTools', () => {
manager.addBemTools({
name: 't1',
item: (props: any) => { return <div />; },
});
expect(manager.getAllBemTools().length).toBe(1);
expect(() => {
manager.addBemTools({
name: 't1',
item: (props: any) => { return <div />; },
});
}).toThrow(/already exists/);
manager.removeBemTools('t2');
expect(manager.getAllBemTools().length).toBe(1);
manager.removeBemTools('t1');
expect(manager.getAllBemTools().length).toBe(0);
});
});

View File

@ -1,510 +0,0 @@
import { IPublicTypePluginMeta } from './../../../../lib/packages/types/src/shell/type/plugin-meta.d';
import '../fixtures/window';
import {
Editor,
globalContext,
Hotkey as InnerHotkey,
Setters as InnerSetters,
} from '@alilc/lowcode-editor-core';
import { Workspace as InnerWorkspace } from '@alilc/lowcode-workspace';
import {
AssetType,
} from '@alilc/lowcode-utils';
import {
IPublicEnumDragObjectType,
} from '@alilc/lowcode-types';
import { Project } from '../../src/project/project';
import pageMetadata from '../fixtures/component-metadata/page';
import { Designer } from '../../src/designer/designer';
import { DocumentModel } from '../../src/document/document-model';
import formSchema from '../fixtures/schema/form';
import { getMockDocument, getMockWindow, getMockEvent, delayObxTick } from '../utils';
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
import { fireEvent } from '@testing-library/react';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
import { Setters, Workspace } from '@alilc/lowcode-shell';
import { ILowCodePluginContextApiAssembler, ILowCodePluginContextPrivate, LowCodePluginManager } from '@alilc/lowcode-designer';
import {
Skeleton as InnerSkeleton,
} from '@alilc/lowcode-editor-skeleton';
describe('Host 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let host: BuiltinSimulatorHost;
beforeAll(() => {
editor = new Editor();
const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
assembleApis: (context: ILowCodePluginContextPrivate, pluginName: string, meta: IPublicTypePluginMeta) => {
context.project = project;
const eventPrefix = meta?.eventPrefix || 'common';
context.workspace = workspace;
},
};
const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler);
const innerWorkspace = new InnerWorkspace(() => {}, {});
const workspace = new Workspace(innerWorkspace);
const innerSkeleton = new InnerSkeleton(editor);
editor.set('skeleton' as any, innerSkeleton);
editor.set('innerHotkey', new InnerHotkey())
editor.set('setters', new Setters(new InnerSetters()));
editor.set('innerPlugins' as any, innerPlugins);
!globalContext.has(Editor) && globalContext.register(editor, Editor);
!globalContext.has('workspace') && globalContext.register(innerWorkspace, 'workspace');
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
designer.createComponentMeta(pageMetadata);
doc = project.createDocument(formSchema);
host = new BuiltinSimulatorHost(designer.project, designer);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer._componentMetasMap.clear();
designer.purge();
host.purge();
designer = null;
project = null;
host = null;
});
describe('基础方法测试', () => {
it('setProps / get / set', async () => {
expect(host.currentDocument).toBe(designer.project.currentDocument);
expect(host.renderEnv).toBe('default');
expect(host.device).toBe('default');
expect(host.deviceClassName).toBeUndefined();
expect(host.requestHandlersMap).toBeNull();
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;}',
},
requestHandlersMap: {},
});
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).toEqual(designer.componentsMap);
expect(host.requestHandlersMap).toEqual({});
host.set('renderEnv', 'vue');
expect(host.renderEnv).toBe('vue');
expect(host.getComponentContext).toThrow('Method not implemented.');
});
it('connect', () => {
const mockFn = jest.fn();
const mockRenderer = { isSimulatorRenderer: true };
host.connect(mockRenderer, mockFn);
expect(host.renderer).toEqual(mockRenderer);
// await delayObxTick();
expect(mockFn).toHaveBeenCalled();
});
it('mountViewport', () => {
const mockBounds = {
top: 10,
bottom: 100,
left: 10,
right: 100,
};
host.mountViewport({
getBoundingClientRect() {
return mockBounds;
},
});
expect(host.viewport.bounds).toEqual(mockBounds);
});
it('autorun', () => {
const mockFn = jest.fn();
host.autorun(mockFn);
expect(mockFn).toHaveBeenCalled();
});
it('purge', () => {
host.purge();
});
it('isEnter', () => {
const mockBounds = {
top: 10,
bottom: 100,
left: 10,
right: 100,
};
host.mountViewport({
getBoundingClientRect() {
return mockBounds;
},
});
expect(
host.isEnter({
globalX: 5,
globalY: 50,
}),
).toBeFalsy();
expect(
host.isEnter({
globalX: 115,
globalY: 50,
}),
).toBeFalsy();
expect(
host.isEnter({
globalX: 50,
globalY: 50,
}),
).toBeTruthy();
expect(
host.isEnter({
globalX: 50,
globalY: 5,
}),
).toBeFalsy();
expect(
host.isEnter({
globalX: 50,
globalY: 150,
}),
).toBeFalsy();
expect(
host.isEnter({
globalX: 150,
globalY: 150,
}),
).toBeFalsy();
});
it('fixEvent', () => {
expect(host.fixEvent({ fixed: true, clientX: 1 })).toEqual({ fixed: true, clientX: 1 });
});
it('findDOMNodes', () => {
host.connect({
findDOMNodes: () => {
return null;
},
}, () => {});
expect(host.findDOMNodes()).toBeNull();
const mockElems = [document.createElement('div')];
host.connect({
findDOMNodes: () => {
return mockElems;
},
}, () => {});
expect(host.findDOMNodes({})).toBe(mockElems);
expect(host.findDOMNodes({}, 'xxx')).toBeNull();
expect(host.findDOMNodes({}, 'div')).toEqual(mockElems);
});
it('getClosestNodeInstance', () => {
const mockFn = jest.fn(() => {
return {
node: {},
nodeId: 'id',
docId: 'docId',
};
});
host.connect({
getClosestNodeInstance: mockFn,
}, () => {});
expect(host.getClosestNodeInstance()).toEqual({
node: {},
nodeId: 'id',
docId: 'docId',
});
});
it('getNodeInstanceFromElement', () => {
expect(host.getNodeInstanceFromElement()).toBeNull();
host.getClosestNodeInstance = () => {
return null;
};
expect(host.getNodeInstanceFromElement({})).toBeNull();
host.getClosestNodeInstance = () => {
return {
docId: project.currentDocument.id,
nodeId: 'xxx',
};
};
expect(host.getNodeInstanceFromElement({})).toBeTruthy();
});
it('getDropContainer', () => {
host.getNodeInstanceFromElement = () => {
return {
node: doc.rootNode,
};
};
host.getDropContainer({
target: {},
dragObject: {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('page')],
},
});
});
it('getComponentInstances', () => {
const mockNode = {
document: { id: 'docId' },
};
host.instancesMap = {
docId: {
get() {
return [{ comp: true }, { comp2: true }];
},
},
};
expect(host.getComponentInstances(mockNode))
.toEqual([{ comp: true }, { comp2: true }]);
const mockInst = { inst: true };
host.getClosestNodeInstance = () => {
return {
instance: mockInst,
};
};
expect(host.getComponentInstances(mockNode, { instance: mockInst }))
.toEqual([{ comp: true }, { comp2: true }]);
});
it('setNativeSelection / setDraggingState / setCopyState / clearState', () => {
const mockFn1 = jest.fn();
const mockFn2 = jest.fn();
const mockFn3 = jest.fn();
const mockFn4 = jest.fn();
host.connect({
setNativeSelection: mockFn1,
setDraggingState: mockFn2,
setCopyState: mockFn3,
clearState: mockFn4,
}, () => {});
host.setNativeSelection(true);
expect(mockFn1).toHaveBeenCalledWith(true);
host.setDraggingState(false);
expect(mockFn2).toHaveBeenCalledWith(false);
host.setCopyState(true);
expect(mockFn3).toHaveBeenCalledWith(true);
host.clearState();
expect(mockFn4).toHaveBeenCalled();
});
it('sensorAvailable / deactiveSensor', () => {
expect(host.sensorAvailable).toBeTruthy();
host.deactiveSensor();
expect(host.sensing).toBeFalsy();
});
it('getComponent', () => {
host.connect({
getComponent: () => {
return {};
},
}, () => {});
expect(host.getComponent()).toEqual({});
expect(host.createComponent()).toBeNull();
expect(host.setSuspense()).toBeFalsy();
});
it('setInstance', () => {
host.instancesMap = {};
host.setInstance('docId1', 'id1', [{}]);
expect(host.instancesMap.docId1.get('id1')).toEqual([{}]);
host.setInstance('docId1', 'id1', null);
expect(host.instancesMap.docId1.get('id1')).toBeUndefined();
});
});
describe('locate 方法', () => {
beforeEach(() => {
const mockBounds = {
top: 10,
bottom: 100,
left: 10,
right: 100,
};
host.mountViewport({
getBoundingClientRect() {
return mockBounds;
},
});
});
it('locate没有 nodes', () => {
expect(host.locate({
dragObject: {
type: IPublicEnumDragObjectType.Node,
nodes: [],
},
})).toBeUndefined();
});
it('locate没有 document', () => {
project.removeDocument(doc);
expect(host.locate({
dragObject: {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('page')],
},
})).toBeNull();
});
it('notFoundComponent', () => {
expect(host.locate({
dragObject: {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('form')],
},
})).toBeUndefined();
})
it('locate', () => {
host.locate({
dragObject: {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('page')],
},
});
});
});
describe('事件测试', () => {
it('setupDragAndClick', () => {});
it('setupContextMenu', async () => {
const mockDocument = getMockDocument();
const mockWindow = getMockWindow(mockDocument);
const mockIframe = {
contentWindow: mockWindow,
contentDocument: mockDocument,
dispatchEvent() {},
};
host.set('library', [
{
package: '@ali/vc-deep',
library: 'lib',
urls: ['a.js', 'b.js'],
},
]);
host.componentsConsumer.consume(() => {});
host.injectionConsumer.consume(() => {});
await host.mountContentFrame(mockIframe);
host.setupContextMenu();
host.getNodeInstanceFromElement = () => {
return {
node: { componentMeta: { componentName: 'Button', getMetadata() { return {} } }, contains() {} },
};
};
const mockFn = jest.fn();
host.designer.editor.on('designer.builtinSimulator.contextmenu', mockFn);
fireEvent.contextMenu(document, {});
// TODO:
// expect(mockFn).toHaveBeenCalledWith({ selected: 'Button' });
});
});
it('事件测试', async () => {
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,
);
});
});

View File

@ -1,9 +0,0 @@
import '../fixtures/window';
import { getMockRenderer } from '../utils';
import { isSimulatorRenderer } from '../../src/builtin-simulator/renderer';
describe('renderer 测试', () => {
it('renderer', () => {
expect(isSimulatorRenderer(getMockRenderer())).toBeTruthy();
});
});

View File

@ -1,60 +0,0 @@
import ResourceConsumer from '../../src/builtin-simulator/resource-consumer';
import { delayObxTick, delay } from '../utils';
it('ResourceConsumer 测试,先消费再监听', async () => {
const con = new ResourceConsumer(() => ({ a: 1, b: 2 }));
const mockFn = jest.fn();
con.consume((data) => {
mockFn(data);
});
await delay(1000);
expect(mockFn).toHaveBeenCalledWith({ a: 1, b: 2 });
con.consume(() => {});
await con.waitFirstConsume();
con.dispose();
});
it('ResourceConsumer 测试先消费再监听isSimulatorRenderer', async () => {
const mockFn = jest.fn();
const con = new ResourceConsumer(() => ({ a: 1, b: 2 }), () => {
const o = { a: 3, b: 4 };
mockFn(o);
return o;
});
con.consume({ isSimulatorRenderer: true });
await delay(1000);
expect(mockFn).toHaveBeenCalledWith({ a: 3, b: 4 });
con.consume(() => {});
await con.waitFirstConsume();
});
it('ResourceConsumer 测试先消费再监听isSimulatorRenderer没有 consume', async () => {
const mockFn = jest.fn();
const con = new ResourceConsumer(() => ({ a: 1, b: 2 }));
con.consume({ isSimulatorRenderer: true });
});
it('ResourceConsumer 测试,先监听再消费', async () => {
const con = new ResourceConsumer(() => ({ a: 1, b: 2 }));
con.waitFirstConsume();
const mockFn = jest.fn();
con.consume((data) => {
mockFn(data);
});
await delay(1000);
expect(mockFn).toHaveBeenCalledWith({ a: 1, b: 2 });
});

View File

@ -1,177 +0,0 @@
import '../../fixtures/window';
import PropTypes from 'prop-types';
import { LowcodeTypes, parseMetadata, parseProps } from '../../../src/builtin-simulator/utils/parse-metadata';
import { default as ReactPropTypesSecret } from 'prop-types/lib/ReactPropTypesSecret';
describe('parseMetadata', () => {
it('parseMetadata', async () => {
const md1 = parseMetadata('Div');
const md2 = parseMetadata({ componentName: 'Div' });
});
it('LowcodeTypes.shape', async () => {
const result = (window as any).PropTypes.shape()
expect(result).toBeDefined();
});
});
describe('LowcodeTypes basic type validators', () => {
it('should validate string types', () => {
const stringValidator = LowcodeTypes.string;
// 对 stringValidator 进行测试
const props = { testProp: 'This is a string' };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = stringValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid string
});
it('should fail with a non-string type', () => {
const stringValidator = LowcodeTypes.string;
const props = { testProp: 42 };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = stringValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-string type
expect(result.message).toContain('Invalid prop `testProp` of type `number` supplied to `TestComponent`, expected `string`.');
});
it('should pass with a valid number', () => {
const numberValidator = LowcodeTypes.number;
const props = { testProp: 42 };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = numberValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid number
});
it('should fail with a non-number type', () => {
const numberValidator = LowcodeTypes.number;
const props = { testProp: 'Not a number' };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = numberValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-number type
expect(result.message).toContain('Invalid prop `testProp` of type `string` supplied to `TestComponent`, expected `number`.');
});
});
describe('Custom type constructors', () => {
it('should create a custom type validator using define', () => {
const customType = LowcodeTypes.define(PropTypes.string, 'customType');
const props = { testProp: 'This is a string' };
const propName = 'testProp';
const componentName = 'TestComponent';
// 测试有效值
const validResult = customType(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(validResult).toBeNull(); // No error for valid string
// 测试无效值
const invalidProps = { testProp: 42 };
const invalidResult = customType(invalidProps, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(invalidResult).toBeInstanceOf(Error); // Error for non-string type
// 验证 lowcodeType 属性
expect(customType.lowcodeType).toEqual('customType');
// 验证 isRequired 属性
const requiredResult = customType.isRequired(invalidProps, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(requiredResult).toBeInstanceOf(Error); // Error for non-string type
});
});
describe('Advanced type constructors', () => {
describe('oneOf Type Validator', () => {
const oneOfValidator = LowcodeTypes.oneOf(['red', 'green', 'blue']);
const propName = 'color';
const componentName = 'ColorPicker';
it('should pass with a valid value', () => {
const props = { color: 'red' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid value
});
it('should fail with an invalid value', () => {
const props = { color: 'yellow' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for invalid value
expect(result.message).toContain(`Invalid prop \`${propName}\` of value \`yellow\` supplied to \`${componentName}\`, expected one of ["red","green","blue"].`);
});
it('should fail with a non-existing value', () => {
const props = { color: 'others' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-existing value
expect(result.message).toContain(`Invalid prop \`${propName}\` of value \`others\` supplied to \`${componentName}\`, expected one of ["red","green","blue"].`);
});
});
});
describe('parseProps function', () => {
it('should correctly parse propTypes and defaultProps', () => {
const component = {
propTypes: {
name: LowcodeTypes.string,
age: LowcodeTypes.number,
},
defaultProps: {
name: 'John Doe',
age: 30,
},
};
const parsedProps = parseProps(component);
// 测试结果长度
expect(parsedProps.length).toBe(2);
// 测试 name 属性
const nameProp: any = parsedProps.find(prop => prop.name === 'name');
expect(nameProp).toBeDefined();
expect(nameProp.propType).toEqual('string');
expect(nameProp.defaultValue).toEqual('John Doe');
// 测试 age 属性
const ageProp: any = parsedProps.find(prop => prop.name === 'age');
expect(ageProp).toBeDefined();
expect(ageProp.propType).toEqual('number');
expect(ageProp.defaultValue).toEqual(30);
});
});
describe('parseProps function', () => {
it('should correctly parse propTypes and defaultProps', () => {
const component = {
propTypes: {
name: LowcodeTypes.string,
age: LowcodeTypes.number,
},
defaultProps: {
name: 'John Doe',
age: 30,
},
};
const parsedProps = parseProps(component);
// 测试结果长度
expect(parsedProps.length).toBe(2);
// 测试 name 属性
const nameProp: any = parsedProps.find(prop => prop.name === 'name');
expect(nameProp).toBeDefined();
expect(nameProp.propType).toEqual('string');
expect(nameProp.defaultValue).toEqual('John Doe');
// 测试 age 属性
const ageProp: any = parsedProps.find(prop => prop.name === 'age');
expect(ageProp).toBeDefined();
expect(ageProp.propType).toEqual('number');
expect(ageProp.defaultValue).toEqual(30);
});
});

View File

@ -1,78 +0,0 @@
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');
});
});

View File

@ -1,22 +0,0 @@
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);
});
});

View File

@ -1,180 +0,0 @@
import '../fixtures/window';
import { getMockWindow, getMockElement, delay } from '../utils';
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import Viewport from '../../src/builtin-simulator/viewport';
import { Designer } from '../../src/designer/designer';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
describe('Viewport 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let viewport: Viewport;
let viewportElem;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
window.DOMRect = class {
constructor(top, left, width, height) {
return { top, left, width, height };
}
};
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
// doc = project.createDocument(formSchema);
});
afterEach(() => {
project.unload();
// project.mountSimulator(undefined);
designer.purge();
designer = null;
project = null;
viewport = null;
});
it('基本函数测试', async () => {
const rect = {
width: 500,
height: 500,
top: 100,
bottom: 500,
left: 100,
right: 500,
};
viewportElem = getMockElement('div', rect);
viewport = new Viewport();
viewport.mount();
expect(viewport.viewportElement).toBeUndefined();
expect(viewport.width).toBe(1000);
expect(viewport.height).toBe(600);
expect(viewport.toGlobalPoint({ left: 0, top: 0 })).toEqual({ left: 0, top: 0 });
expect(viewport.toLocalPoint({ left: 0, top: 0 })).toEqual({ left: 0, top: 0 });
viewport.mount(viewportElem);
expect(viewport.viewportElement).toBe(viewportElem);
expect(viewport.bounds).toEqual(rect);
expect(viewport.contentBounds).toEqual({ top: 0, left: 0, width: 500, height: 500 });
expect(viewport.rect).toEqual(rect);
expect(viewport.width).toBe(500);
expect(viewport.contentWidth).toBe('100%');
expect(viewport.height).toBe(500);
expect(viewport.contentHeight).toBe('100%');
await delay(100);
viewportElem.setWidth(300);
viewport.width = 300;
expect(viewport.width).toBe(300);
await delay(100);
viewportElem.setHeight(300);
viewport.height = 300;
expect(viewport.height).toBe(300);
viewport.contentWidth = 200;
expect(viewport.contentWidth).toBe(200);
viewport.contentHeight = 200;
expect(viewport.contentHeight).toBe(200);
});
it('scale', () => {
const rect = {
width: 500,
height: 500,
top: 100,
bottom: 500,
left: 100,
right: 500,
};
viewportElem = getMockElement('div', rect);
viewport = new Viewport();
viewport.mount(viewportElem);
expect(viewport.scale).toBe(1);
viewport.scale = 2;
expect(viewport.scale).toBe(2);
expect(viewport.contentWidth).toBe(500 / 2);
expect(viewport.contentHeight).toBe(500 / 2);
viewport.width = 300;
viewportElem.setWidth(300);
expect(viewport.contentWidth).toBe(300 / 2);
viewport.height = 300;
viewportElem.setHeight(300);
expect(viewport.contentHeight).toBe(300 / 2);
expect(() => { viewport.scale = NaN; }).toThrow();
expect(() => { viewport.scale = -1; }).toThrow();
});
it('setScrollTarget / scrollTarget / scrolling', async () => {
const rect = {
width: 500,
height: 500,
top: 100,
bottom: 500,
left: 100,
right: 500,
};
viewportElem = getMockElement('div', rect);
viewport = new Viewport();
viewport.mount(viewportElem);
const mockWindow = getMockWindow();
viewport.setScrollTarget(mockWindow);
// TODO: 待 mock
viewport.scrollTarget;
// expect(viewport.scrollTarget).toBe(mockWindow);
// mock scrollTarget
// viewport._scrollTarget = { left: 0, top: 0 };
// viewport._scrollTarget.left = 123;
// viewport._scrollTarget.top = 1234;
mockWindow.triggerEventListener('scroll');
expect(viewport.scrolling).toBeTruthy();
// TODO: 待 mock
viewport.scrollX;
viewport.scrollY;
// expect(viewport.scrollX).toBe(123);
// expect(viewport.scrollY).toBe(1234);
await delay(100);
expect(viewport.scrolling).toBeFalsy();
mockWindow.triggerEventListener('resize');
});
it('toGlobalPoint / toLocalPoint', () => {
const rect = {
width: 500,
height: 500,
top: 100,
bottom: 500,
left: 100,
right: 500,
};
viewportElem = getMockElement('div', rect);
viewport = new Viewport();
viewport.mount(viewportElem);
expect(viewport.toGlobalPoint({ clientX: 100, clientY: 100 })).toEqual({ clientX: 200, clientY: 200 });
expect(viewport.toLocalPoint({ clientX: 200, clientY: 200 })).toEqual({ clientX: 100, clientY: 100 });
viewport.scale = 2;
expect(viewport.toGlobalPoint({ clientX: 100, clientY: 100 })).toEqual({ clientX: 300, clientY: 300 });
expect(viewport.toLocalPoint({ clientX: 300, clientY: 300 })).toEqual({ clientX: 100, clientY: 100 });
});
});

View File

@ -1,41 +0,0 @@
import '../fixtures/window';
import { ActiveTracker } from '../../src/designer/active-tracker';
it('ActiveTracker 测试Node', () => {
const tracker = new ActiveTracker();
const mockFn = jest.fn();
const mockNode = { isNode: true };
const off = tracker.onChange(mockFn);
tracker.track(mockNode);
expect(mockFn).toHaveBeenCalledWith({ node: mockNode });
expect(tracker.currentNode).toBe(mockNode);
off();
mockFn.mockClear();
tracker.track(mockNode);
expect(mockFn).not.toHaveBeenCalled();
});
it('ActiveTracker 测试ActiveTarget', () => {
const tracker = new ActiveTracker();
const mockFn = jest.fn();
const mockNode = { isNode: true };
const off = tracker.onChange(mockFn);
const mockTarget = { node: mockNode, detail: { isDetail: true }, instance: { isInstance: true } };
tracker.track(mockTarget);
expect(mockFn).toHaveBeenCalledWith(mockTarget);
expect(tracker.currentNode).toBe(mockNode);
expect(tracker.detail).toEqual({ isDetail: true });
expect(tracker.instance).toEqual({ isInstance: true });
off();
mockFn.mockClear();
tracker.track(mockNode);
expect(mockFn).not.toHaveBeenCalled();
});

View File

@ -1,395 +0,0 @@
import '../fixtures/window';
import {
Editor,
globalContext,
Hotkey as InnerHotkey,
} from '@alilc/lowcode-editor-core';
import { Designer } from '../../src/designer/designer';
import formSchema from '../fixtures/schema/form';
import { fireEvent } from '@testing-library/react';
import { builtinHotkey } from '../../../engine/src/inner-plugins/builtin-hotkey';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
import { ILowCodePluginContextPrivate, LowCodePluginManager } from '@alilc/lowcode-designer';
import { IPublicApiPlugins } from '@alilc/lowcode-types';
import { Logger, Project, Canvas } from '@alilc/lowcode-shell';
import { Workspace } from '@alilc/lowcode-workspace';
const editor = new Editor();
const workspace = new Workspace();
let designer: Designer;
// keyCode 对应表https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
// hotkey 模块底层用的 keyCode所以还不能用 key / code 测试
describe('快捷键测试', () => {
let pluginManager: LowCodePluginManager;
let project: any = {};
beforeAll(() => {
return new Promise((resolve, reject) => {
const hotkey: any = new InnerHotkey();
const logger = new Logger({ level: 'warn', bizName: 'common' });
const contextApiAssembler = {
assembleApis(context: ILowCodePluginContextPrivate){
context.plugins = pluginManager as IPublicApiPlugins;
context.hotkey = hotkey;
context.logger = logger;
context.project = project;
context.canvas = new Canvas(editor);
}
};
pluginManager = new LowCodePluginManager(contextApiAssembler).toProxy();
pluginManager.register(builtinHotkey);
globalContext.register(editor, Editor);
globalContext.register(editor, 'editor');
globalContext.register(workspace, 'workspace');
pluginManager.init().then(() => {
resolve({});
});
})
});
afterAll(() => {
pluginManager.dispose();
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
editor.set('designer', designer);
designer.project.open(formSchema);
project.__proto__ = new Project(designer.project);
});
afterEach(() => {
designer = null;
});
it('right', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbj')!;
firstCardNode.select();
fireEvent.keyDown(document, { keyCode: 39 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy();
});
it('left', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
firstCardNode.select();
fireEvent.keyDown(document, { keyCode: 37 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbj')).toBeTruthy();
});
it('down', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
firstCardNode.select();
fireEvent.keyDown(document, { keyCode: 40 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbo')).toBeTruthy();
});
it('up', () => {
const secondCardNode = designer.currentDocument?.getNode('node_k1ow3cbm')!;
secondCardNode.select();
fireEvent.keyDown(document, { keyCode: 38 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy();
});
// 跟右侧节点调换位置
it('option + right', () => {
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
firstButtonNode.select();
fireEvent.keyDown(document, { keyCode: 39, altKey: true });
expect(firstButtonNode.prevSibling?.getId()).toBe('node_k1ow3cbp');
});
// 跟左侧节点调换位置
it('option + left', () => {
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
secondButtonNode.select();
fireEvent.keyDown(document, { keyCode: 37, altKey: true });
expect(secondButtonNode.nextSibling?.getId()).toBe('node_k1ow3cbn');
});
// 向父级移动该节点
it('option + up', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
firstCardNode.select();
fireEvent.keyDown(document, { keyCode: 38, altKey: true });
});
// 将节点移入到兄弟节点中
it('option + up', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
firstCardNode.select();
fireEvent.keyDown(document, { keyCode: 40, altKey: true });
});
// 撤销
it('command + z', async () => {
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
let secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
// 等待第一个 session 结束
await new Promise(resolve => setTimeout(resolve, 1000));
firstButtonNode.remove();
expect(secondButtonNode.getParent()?.children.size).toBe(1);
await new Promise(resolve => setTimeout(resolve, 1000));
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
// 重新获取一次节点,因为 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')!;
// 等待第一个 session 结束
await new Promise(resolve => setTimeout(resolve, 1000));
firstButtonNode.remove();
expect(secondButtonNode.getParent()?.children.size).toBe(1);
await new Promise(resolve => setTimeout(resolve, 1000));
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
expect(secondButtonNode.getParent()?.children.size).toBe(2);
await new Promise(resolve => setTimeout(resolve, 1000));
fireEvent.keyDown(document, { keyCode: 89, metaKey: true });
// 重新获取一次节点,因为 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();
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
});
it('command + v', async () => {
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
secondButtonNode.select();
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
fireEvent.keyDown(document, { keyCode: 86, metaKey: true });
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();
fireEvent.keyDown(document, { keyCode: 27 });
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');
fireEvent.keyDown(document, { keyCode: 46 });
expect(secondButtonNode.prevSibling).toBeNull();
});
describe('非正常分支', () => {
it('liveEditing mode', () => {
designer.project.mountSimulator({
liveEditing: {
editing: {},
},
});
editor.set('designer', designer);
designer.currentDocument?.selection.select('page');
// nothing happened
fireEvent.keyDown(document, { keyCode: 39 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 37 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 40 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 38 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 39, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 37, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 40, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 38, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 89, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 86, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 27 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(document, { keyCode: 46 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
});
it('isFormEvent: true', () => {
const inputDOMNode = document.createElement('INPUT');
document.body.appendChild(inputDOMNode);
designer.currentDocument?.selection.select('page');
// nothing happened
fireEvent.keyDown(inputDOMNode, { keyCode: 39 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 37 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 40 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 38 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 39, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 37, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 40, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 38, altKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 90, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 89, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 67, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 86, metaKey: true });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 27 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
fireEvent.keyDown(inputDOMNode, { keyCode: 46 });
expect(designer.currentDocument?.selection.selected[0]).toBe('page');
});
it('doc is null', () => {
designer.currentDocument?.selection.select('page');
designer.project.documents = [];
fireEvent.keyDown(document, { keyCode: 39 });
fireEvent.keyDown(document, { keyCode: 37 });
fireEvent.keyDown(document, { keyCode: 40 });
fireEvent.keyDown(document, { keyCode: 38 });
fireEvent.keyDown(document, { keyCode: 39, altKey: true });
fireEvent.keyDown(document, { keyCode: 37, altKey: true });
fireEvent.keyDown(document, { keyCode: 40, altKey: true });
fireEvent.keyDown(document, { keyCode: 38, altKey: true });
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
fireEvent.keyDown(document, { keyCode: 89, metaKey: true });
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
fireEvent.keyDown(document, { keyCode: 86, metaKey: true });
fireEvent.keyDown(document, { keyCode: 27 });
fireEvent.keyDown(document, { keyCode: 46 });
});
it('selected is []', () => {
fireEvent.keyDown(document, { keyCode: 39 });
fireEvent.keyDown(document, { keyCode: 37 });
fireEvent.keyDown(document, { keyCode: 40 });
fireEvent.keyDown(document, { keyCode: 38 });
fireEvent.keyDown(document, { keyCode: 39, altKey: true });
fireEvent.keyDown(document, { keyCode: 37, altKey: true });
fireEvent.keyDown(document, { keyCode: 40, altKey: true });
fireEvent.keyDown(document, { keyCode: 38, altKey: true });
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
fireEvent.keyDown(document, { keyCode: 89, metaKey: true });
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
fireEvent.keyDown(document, { keyCode: 86, metaKey: true });
fireEvent.keyDown(document, { keyCode: 27 });
fireEvent.keyDown(document, { keyCode: 46 });
});
});
});

View File

@ -1,500 +0,0 @@
import '../fixtures/window';
import { Editor, globalContext, Setters } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import { Designer } from '../../src/designer/designer';
import { Dragon } from '../../src/designer/dragon';
// import { TransformStage } from '../../src/document/node/transform-stage';
import formSchema from '../fixtures/schema/form';
import buttonMetadata from '../fixtures/component-metadata/button';
import pageMetadata from '../fixtures/component-metadata/page';
import divMetadata from '../fixtures/component-metadata/div';
import { delayObxTick } from '../utils';
import { fireEvent } from '@testing-library/react';
import { IPublicEnumDragObjectType, IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
const mockNode = {
internalToShellNode() {
return 'mockNode';
},
};
describe('Designer 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let dragon: Dragon;
beforeAll(() => {
editor = new Editor();
const setters = new Setters();
editor.set('setters', setters);
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = project.createDocument(formSchema);
dragon = new Dragon(designer);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer.purge();
designer = null;
project = null;
dragon = null;
});
describe('onDragstart / onDrag / onDragend', () => {
it('IPublicEnumDragObjectType.Node', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const dragStartMockFn2 = jest.fn();
const dragMockFn2 = jest.fn();
const dragEndMockFn2 = jest.fn();
const designer = new Designer({
editor,
shellModelFactory,
onDragstart: dragStartMockFn,
onDrag: dragMockFn,
onDragend: dragEndMockFn,
});
editor.on('designer.dragstart', dragStartMockFn2);
editor.on('designer.drag', dragMockFn2);
editor.on('designer.dragend', dragEndMockFn2);
const { dragon } = designer;
dragon.boost(
{
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
expect(dragStartMockFn).toHaveBeenCalledTimes(1);
expect(dragStartMockFn2).toHaveBeenCalledTimes(1);
expect(dragMockFn).toHaveBeenCalledTimes(1);
expect(dragMockFn2).toHaveBeenCalledTimes(1);
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
expect(dragMockFn).toHaveBeenCalledTimes(2);
expect(dragMockFn2).toHaveBeenCalledTimes(2);
setMockDropLocation();
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragEndMockFn).toHaveBeenCalledTimes(1);
expect(dragEndMockFn2).toHaveBeenCalledTimes(1);
function setMockDropLocation() {
const mockTarget = {
document: doc,
children: {
get(x) {
return x;
},
insert() {},
internalInsert() {},
},
};
const mockDetail = { type: 'Children', index: 1, near: { node: { x: 1 } } };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
return designer.createLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
}
});
it('IPublicEnumDragObjectType.NodeData', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const dragStartMockFn2 = jest.fn();
const dragMockFn2 = jest.fn();
const dragEndMockFn2 = jest.fn();
const designer = new Designer({
editor,
shellModelFactory,
onDragstart: dragStartMockFn,
onDrag: dragMockFn,
onDragend: dragEndMockFn,
});
editor.on('designer.dragstart', dragStartMockFn2);
editor.on('designer.drag', dragMockFn2);
editor.on('designer.dragend', dragEndMockFn2);
const { dragon } = designer;
dragon.boost(
{
type: IPublicEnumDragObjectType.NodeData,
data: [{
componentName: 'Button',
}],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
expect(dragStartMockFn).toHaveBeenCalledTimes(1);
expect(dragStartMockFn2).toHaveBeenCalledTimes(1);
expect(dragMockFn).toHaveBeenCalledTimes(1);
expect(dragMockFn2).toHaveBeenCalledTimes(1);
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
expect(dragMockFn).toHaveBeenCalledTimes(2);
expect(dragMockFn2).toHaveBeenCalledTimes(2);
setMockDropLocation();
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragEndMockFn).toHaveBeenCalledTimes(1);
expect(dragEndMockFn2).toHaveBeenCalledTimes(1);
function setMockDropLocation() {
const mockTarget = {
document: doc,
children: {
get(x) {
return x;
},
insert() {},
internalInsert() {},
},
};
const mockDetail = { type: 'Children', index: 1, near: { node: { x: 1 } } };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
return designer.createLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
}
});
});
it('addPropsReducer / transformProps', () => {
// 没有相应的 reducer
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Init)).toEqual({ num: 1 });
// props 是数组
expect(designer.transformProps([{ num: 1 }], mockNode, IPublicEnumTransformStage.Init)).toEqual([{ num: 1 }]);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Init);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Init);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Clone);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Serilize);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Render);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Save);
designer.addPropsReducer((props, node) => {
props.num += 1;
return props;
}, IPublicEnumTransformStage.Upgrade);
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Init)).toEqual({ num: 3 });
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Clone)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Serilize)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Render)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Save)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Upgrade)).toEqual({ num: 2 });
designer.addPropsReducer((props, node) => {
throw new Error('calculate error');
}, IPublicEnumTransformStage.Upgrade);
expect(designer.transformProps({ num: 1 }, mockNode, IPublicEnumTransformStage.Upgrade)).toEqual({ num: 2 });
});
it('setProps', () => {
// 第一次设置 props
const initialProps = {
simulatorComponent: { isSimulatorComp: true },
simulatorProps: { designMode: 'design' },
suspensed: true,
componentMetadatas: [buttonMetadata, divMetadata],
};
designer = new Designer({
editor,
shellModelFactory,
...initialProps,
});
expect(designer.simulatorComponent).toEqual({ isSimulatorComp: true });
expect(designer.simulatorProps).toEqual({ designMode: 'design' });
expect(designer.suspensed).toBeTruthy();
expect((designer as any)._componentMetasMap.has('Div')).toBeTruthy();
expect((designer as any)._componentMetasMap.has('Button')).toBeTruthy();
const { editor: editorFromDesigner, shellModelFactory: shellModelFactoryFromDesigner, ...others } = (designer as any).props;
expect(others).toEqual(initialProps);
expect(designer.get('simulatorProps')).toEqual({ designMode: 'design' });
expect(designer.get('suspensed')).toBeTruthy();
expect(designer.get('xxx')).toBeUndefined();
// 第二次设置 props
const updatedProps = {
simulatorComponent: { isSimulatorComp2: true },
simulatorProps: { designMode: 'live' },
suspensed: false,
componentMetadatas: [buttonMetadata],
};
designer.setProps(updatedProps);
expect(designer.simulatorComponent).toEqual({ isSimulatorComp2: true });
expect(designer.simulatorProps).toEqual({ designMode: 'live' });
expect(designer.suspensed).toBeFalsy();
expect((designer as any)._componentMetasMap.has('Button')).toBeTruthy();
expect((designer as any)._componentMetasMap.has('Div')).toBeTruthy();
const { editor: editorFromDesigner2, shellModelFactory: shellModelFactoryFromDesigner2, ...others2 } = (designer as any).props;
expect(others2).toEqual(updatedProps);
// 第三次设置 props跟第二次值一样for 覆盖率测试
const updatedProps2 = updatedProps;
designer.setProps(updatedProps2);
expect(designer.simulatorComponent).toEqual({ isSimulatorComp2: true });
expect(designer.simulatorProps).toEqual({ designMode: 'live' });
expect(designer.suspensed).toBeFalsy();
expect((designer as any)._componentMetasMap.has('Button')).toBeTruthy();
expect((designer as any)._componentMetasMap.has('Div')).toBeTruthy();
const { editor: editorFromDesigner3, shellModelFactory: shellModelFactoryFromDesigner3, ...others3 } = (designer as any).props;
expect(others3).toEqual(updatedProps);
});
describe('getSuitableInsertion', () => {
it('没有 currentDocument', () => {
project.unload();
expect(designer.getSuitableInsertion({})).toBeNull();
});
it('有选中节点isContainer && 允许放子节点', () => {
designer.createComponentMeta(divMetadata);
designer.createComponentMeta(buttonMetadata);
designer.currentSelection?.select('node_k1ow3cbo');
const { target, index } = designer.getSuitableInsertion(
doc.createNode({ componentName: 'Button' }),
);
expect(target).toBe(doc.getNode('node_k1ow3cbo'));
expect(index).toBeUndefined();
});
it('有选中节点,不是 isContainer', () => {
designer.createComponentMeta(divMetadata);
designer.createComponentMeta(buttonMetadata);
designer.currentSelection?.select('node_k1ow3cbn');
const { target, index } = designer.getSuitableInsertion(
doc.createNode({ componentName: 'Button' }),
);
expect(target).toBe(doc.getNode('node_k1ow3cbo'));
expect(index).toBe(1);
});
it('无选中节点', () => {
designer.createComponentMeta(pageMetadata);
const { target, index } = designer.getSuitableInsertion(
doc.createNode({ componentName: 'Button' }),
);
expect(target).toBe(doc.getNode('page'));
expect(index).toBeUndefined();
});
});
it('getComponentMetasMap', () => {
designer.createComponentMeta({
componentName: 'Div',
title: '容器',
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
devMode: 'procode',
tags: ['布局'],
});
expect(designer.getComponentMetasMap().get('Div')).not.toBeUndefined();
});
it('refreshComponentMetasMap', () => {
designer.createComponentMeta({
componentName: 'Div',
title: '容器',
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
devMode: 'procode',
tags: ['布局'],
});
const originalMetasMap = designer.getComponentMetasMap();
designer.refreshComponentMetasMap();
expect(originalMetasMap).not.toBe(designer.getComponentMetasMap());
});
describe('loadIncrementalAssets', () => {
it('components && packages', async () => {
editor.set('assets', { components: [], packages: [] });
const fn = jest.fn();
project.mountSimulator({
setupComponents: fn,
});
await designer.loadIncrementalAssets({
components: [{
componentName: 'Div2',
title: '容器',
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
devMode: 'proCode',
tags: ['布局'],
}],
packages: [],
});
const comps = editor.get('assets').components;
expect(comps).toHaveLength(1);
expect(fn).toHaveBeenCalled();
});
it('no components && packages', async () => {
editor.set('assets', { components: [], packages: [] });
const fn = jest.fn();
project.mountSimulator({
setupComponents: fn,
});
await designer.loadIncrementalAssets({});
expect(fn).not.toHaveBeenCalled();
});
});
it('createLocation / clearLocation', () => {
const mockTarget = {
document: doc,
children: {
get(x) {
return x;
},
insert() {},
internalInsert() {},
},
};
const mockDetail = { type: 'Children', index: 1, near: { node: { x: 1 } } };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = designer.createLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(designer.dropLocation).toBe(loc);
const doc2 = project.createDocument({ componentName: 'Page' });
designer.createLocation({
target: {
document: doc2,
children: {
get(x) {
return x;
},
insert() {},
internalInsert() {},
},
},
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
designer.clearLocation();
expect(designer.dropLocation).toBeUndefined();
});
it('autorun', async () => {
const mockFn = jest.fn();
designer.autorun(() => {
mockFn();
}, true);
await delayObxTick();
expect(mockFn).toHaveBeenCalled();
});
it('suspensed', () => {
designer.suspensed = true;
expect(designer.suspensed).toBeTruthy();
designer.suspensed = false;
expect(designer.suspensed).toBeFalsy();
});
it('schema', () => {
// TODO: matchSnapshot
designer.schema;
designer.setSchema({
componentsTree: [
{
componentName: 'Page',
props: {},
},
],
});
});
it('createOffsetObserver / clearOobxList / touchOffsetObserver', () => {
project.mountSimulator({
computeComponentInstanceRect() {},
});
designer.createOffsetObserver({ node: doc.getNode('page'), instance: {} });
expect(designer.oobxList).toHaveLength(1);
designer.createOffsetObserver({ node: doc.getNode('page'), instance: {} });
expect(designer.oobxList).toHaveLength(2);
designer.clearOobxList(true);
expect(designer.oobxList).toHaveLength(0);
const obx = designer.createOffsetObserver({ node: doc.getNode('page'), instance: {} });
obx.pid = 'xxx';
obx.compute = () => {};
expect(designer.oobxList).toHaveLength(1);
designer.touchOffsetObserver();
expect(designer.oobxList).toHaveLength(1);
});
});

View File

@ -1,26 +0,0 @@
import { Detecting } from '../../src/designer/detecting';
it('Detecting 测试', () => {
const fn = jest.fn();
const detecting = new Detecting();
detecting.onDetectingChange(fn);
expect(detecting.enable).toBeTruthy();
const mockNode = { document };
detecting.capture(mockNode);
expect(fn).toHaveBeenCalledWith(detecting.current);
expect(detecting.current).toBe(mockNode);
detecting.release({});
detecting.release(mockNode);
expect(detecting.current).toBeNull();
detecting.capture(mockNode);
detecting.leave(document);
expect(detecting.current).toBeNull();
detecting.capture(mockNode);
detecting.enable = false;
expect(detecting.current).toBeNull();
});

View File

@ -1,369 +0,0 @@
import '../fixtures/window';
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import { Designer } from '../../src/designer/designer';
import {
Dragon,
isDragNodeObject,
isDragNodeDataObject,
isDragAnyObject,
isLocateEvent,
isShaken,
setShaken,
isInvalidPoint,
isSameAs,
} from '../../src/designer/dragon';
import { IPublicEnumDragObjectType } from '@alilc/lowcode-types';
import formSchema from '../fixtures/schema/form';
import { fireEvent } from '@testing-library/react';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
describe('Dragon 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let dragon: Dragon;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = project.createDocument(formSchema);
dragon = new Dragon(designer);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer.purge();
designer = null;
project = null;
dragon = null;
});
it.skip('drag NodeData', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
dragon.onDragstart((e) => {
console.log('start', e, e.originalEvent, e.originalEvent.clientX);
});
dragon.onDrag((e) => {
console.log('drag', e, e.originalEvent, e.originalEvent.clientX);
});
dragon.onDragend((e) => {
console.log('end', e, e.originalEvent);
});
dragon.boost(
{
type: IPublicEnumDragObjectType.NodeData,
data: [{ componentName: 'Button' }],
},
new Event('dragstart', { clientX: 100, clientY: 100 }),
);
fireEvent.dragOver(document, { clientX: 108, clientY: 108 });
fireEvent.dragEnd(document, { clientX: 118, clientY: 118 });
});
it.skip('drag Node', () => {
console.log(new MouseEvent('mousedown', { clientX: 1 }).clientX);
// console.log(new Event('mousedown', { clientX: 1 }).clientX);
// console.log(new Event('drag', { clientX: 1 }).clientX);
// console.log(new CustomEvent('drag', { clientX: 1 }).clientX);
console.log(document.createEvent('dragstart', { clientX: 1 }).clientX);
});
it('mouse NodeData', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const offDragStart = dragon.onDragstart(dragStartMockFn);
const offDrag = dragon.onDrag(dragMockFn);
const offDragEnd = dragon.onDragend(dragEndMockFn);
dragon.boost(
{
type: IPublicEnumDragObjectType.NodeData,
data: [{ componentName: 'Button' }],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragStartMockFn).toHaveBeenCalledTimes(1);
expect(dragMockFn).toHaveBeenCalledTimes(2);
expect(dragEndMockFn).toHaveBeenCalledTimes(1);
});
it('mouse Node', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const offDragStart = dragon.onDragstart(dragStartMockFn);
const offDrag = dragon.onDrag(dragMockFn);
const offDragEnd = dragon.onDragend(dragEndMockFn);
dragon.boost(
{
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
// mouseDown 模式正常不会触发 dragStart 事件,除非 shaken 型
expect(dragStartMockFn).not.toHaveBeenCalled();
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
expect(dragStartMockFn).toHaveBeenCalledTimes(1);
expect(dragMockFn).toHaveBeenCalledTimes(1);
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
expect(dragMockFn).toHaveBeenCalledTimes(2);
expect(dragon.dragging).toBeTruthy();
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragEndMockFn).toHaveBeenCalledTimes(1);
offDragStart();
offDrag();
offDragEnd();
dragMockFn.mockClear();
dragon.boost(
{
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
expect(dragMockFn).not.toHaveBeenCalled();
});
it('mouse Node & esc', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const offDragStart = dragon.onDragstart(dragStartMockFn);
const offDrag = dragon.onDrag(dragMockFn);
const offDragEnd = dragon.onDragend(dragEndMockFn);
dragon.boost(
{
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.keyDown(document, { keyCode: 27 });
expect(dragon.designer.dropLocation).toBeUndefined();
});
it('mouse Node & copy', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const offDragStart = dragon.onDragstart(dragStartMockFn);
const offDrag = dragon.onDrag(dragMockFn);
const offDragEnd = dragon.onDragend(dragEndMockFn);
dragon.boost(
{
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
const mockFn1 = jest.fn();
project.mountSimulator({ setCopyState: mockFn1 });
expect(dragon.getSimulators().size).toBe(1);
fireEvent.keyDown(document, { ctrlKey: true });
expect(mockFn1).toHaveBeenCalled();
});
it('from', () => {
const dragStartMockFn = jest.fn();
const dragMockFn = jest.fn();
const dragEndMockFn = jest.fn();
const offDragStart = dragon.onDragstart(dragStartMockFn);
const offDrag = dragon.onDrag(dragMockFn);
const offDragEnd = dragon.onDragend(dragEndMockFn);
const mockBoostFn = jest
.fn((e) => {
return {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
};
})
.mockImplementationOnce(() => null);
const offFrom = dragon.from(document, mockBoostFn);
// 无用 mouseDown无效的按钮
fireEvent.mouseDown(document, { button: 2 });
expect(dragStartMockFn).not.toHaveBeenCalled();
// 无用 mouseDown无效的 dragObject
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
expect(dragStartMockFn).not.toHaveBeenCalled();
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
expect(dragStartMockFn).not.toHaveBeenCalled();
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
expect(dragStartMockFn).toHaveBeenCalledTimes(1);
expect(dragMockFn).toHaveBeenCalledTimes(1);
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
expect(dragMockFn).toHaveBeenCalledTimes(2);
expect(dragon.dragging).toBeTruthy();
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragEndMockFn).toHaveBeenCalledTimes(1);
offDragStart();
offDrag();
offDragEnd();
dragMockFn.mockClear();
fireEvent.mouseMove(document, { clientX: 100, clientY: 100 });
expect(dragMockFn).not.toHaveBeenCalled();
offFrom();
fireEvent.mouseMove(document, { clientX: 100, clientY: 100 });
expect(dragMockFn).not.toHaveBeenCalled();
});
it('addSensor / removeSensor', () => {
const sensor = {
locate: () => {},
sensorAvailable: true,
isEnter: () => true,
fixEvent: () => {},
deactiveSensor: () => {},
};
const sensor2 = {};
dragon.addSensor(sensor);
expect(dragon.sensors.length).toBe(1);
expect(dragon.activeSensor).toBeUndefined();
dragon.boost(
{
type: IPublicEnumDragObjectType.NodeData,
data: [{ componentName: 'Button' }],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
expect(dragon.activeSensor).toBe(sensor);
// remove a non-existing sensor
dragon.removeSensor(sensor2);
expect(dragon.sensors.length).toBe(1);
dragon.removeSensor(sensor);
expect(dragon.sensors.length).toBe(0);
});
it('has sensor', () => {
const mockFn1 = jest.fn();
const mockDoc = document.createElement('iframe').contentWindow?.document;
dragon.addSensor({
fixEvent: () => {},
locate: () => {},
contentDocument: mockDoc,
});
project.mountSimulator({
setCopyState: mockFn1,
setNativeSelection: () => {},
clearState: () => {},
setDraggingState: () => {},
});
const mockBoostFn = jest
.fn((e) => {
return {
type: IPublicEnumDragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
};
})
.mockImplementationOnce(() => null);
const offFrom = dragon.from(document, mockBoostFn);
// TODO: 想办法 mock 一个 iframe.currentDocument
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
});
});
describe('导出的其他函数', () => {
it('isDragNodeObject', () => {
expect(isDragNodeObject({ type: IPublicEnumDragObjectType.Node, nodes: [] })).toBeTruthy();
});
it('isDragNodeDataObject', () => {
expect(isDragNodeDataObject({ type: IPublicEnumDragObjectType.NodeData, data: [] })).toBeTruthy();
});
it('isDragAnyObject', () => {
expect(isDragAnyObject()).toBeFalsy();
expect(isDragAnyObject({ type: IPublicEnumDragObjectType.Node, nodes: [] })).toBeFalsy();
expect(isDragAnyObject({ type: IPublicEnumDragObjectType.NodeData, data: [] })).toBeFalsy();
expect(isDragAnyObject({ type: 'others', data: [] })).toBeTruthy();
});
it('isLocateEvent', () => {
expect(isLocateEvent({ type: 'LocateEvent' })).toBeTruthy();
});
it('isShaken', () => {
expect(
isShaken(
{ clientX: 1, clientY: 1, target: {} },
{ clientX: 1, clientY: 1, target: { other: 1 } },
),
).toBeTruthy();
expect(isShaken({ shaken: true })).toBeTruthy();
expect(isShaken({ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 2 })).toBeFalsy();
expect(isShaken({ clientX: 1, clientY: 1 }, { clientX: 3, clientY: 5 })).toBeTruthy();
});
it('setShaken', () => {
const e = {};
setShaken(e);
expect(isShaken(e)).toBeTruthy();
});
it('isInvalidPoint', () => {
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 6, clientY: 1 })).toBeTruthy();
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 1, clientY: 6 })).toBeTruthy();
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 6, clientY: 6 })).toBeTruthy();
expect(isInvalidPoint({ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 1 })).toBeFalsy();
});
it('isSameAs', () => {
expect(isSameAs({ clientX: 1, clientY: 1 }, { clientX: 1, clientY: 1 })).toBeTruthy();
expect(isSameAs({ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 1 })).toBeFalsy();
});
});

View File

@ -1,209 +0,0 @@
import {
DropLocation,
isLocationData,
isLocationChildrenDetail,
isRowContainer,
isChildInline,
getRectTarget,
isVerticalContainer,
isVertical,
getWindow,
} from '../../src/designer/location';
import { getMockElement } from '../utils';
describe('DropLocation 测试', () => {
it('constructor', () => {
const mockTarget = { document };
const mockDetail = {};
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = new DropLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(loc.getContainer()).toBe(mockTarget);
expect(loc.document).toBe(document);
expect(loc.target).toBe(mockTarget);
expect(loc.detail).toBe(mockDetail);
expect(loc.source).toBe(mockSource);
expect(loc.event).toBe(mockEvent);
const mockEvent2 = { type: 'LocateEvent', data: [] };
const loc2 = loc.clone(mockEvent2);
expect(loc2.target).toBe(mockTarget);
expect(loc2.detail).toBe(mockDetail);
expect(loc2.source).toBe(mockSource);
expect(loc2.event).toBe(mockEvent2);
});
it('constructor, detail: undefined', () => {
const mockTarget = { document };
const mockDetail = undefined;
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = new DropLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(loc.getInsertion()).toBeNull();
});
it('constructor, detail.type: Children, detail.index <= 0', () => {
const mockTarget = { document };
const mockDetail = { type: 'Children', index: -1 };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = new DropLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(loc.getInsertion()).toBeNull();
});
it('constructor, detail.type: Children, detail.index > 0', () => {
const mockTarget = {
document,
children: {
get(x) {
return x;
},
},
};
const mockDetail = { type: 'Children', index: 1 };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = new DropLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(loc.getInsertion()).toBe(0);
});
it('constructor, detail.type: Prop', () => {
const mockTarget = {
document,
children: {
get(x) {
return x;
},
},
};
const mockDetail = { type: 'Prop', index: 1, near: { node: { x: 1 } } };
const mockSource = {};
const mockEvent = { type: 'LocateEvent', nodes: [] };
const loc = new DropLocation({
target: mockTarget,
detail: mockDetail,
source: mockSource,
event: mockEvent,
});
expect(loc.getInsertion()).toEqual({ x: 1 });
});
});
it('isLocationData', () => {
expect(isLocationData({ target: {}, detail: {} })).toBeTruthy();
});
it('isLocationChildrenDetail', () => {
expect(isLocationChildrenDetail({ type: 'Children' })).toBeTruthy();
});
it('isRowContainer', () => {
expect(isRowContainer({ nodeType: Node.TEXT_NODE })).toBeTruthy();
window.getComputedStyle = jest
.fn(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'flex' : '';
},
};
})
.mockImplementationOnce(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'flex' : 'column';
},
};
})
.mockImplementationOnce(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'grid' : 'column';
},
};
});
expect(isRowContainer(getMockElement('div'))).toBeFalsy();
expect(isRowContainer(getMockElement('div'))).toBeTruthy();
expect(isRowContainer(getMockElement('div'))).toBeTruthy();
});
it('isChildInline', () => {
window.getComputedStyle = jest
.fn(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'inline' : 'float';
},
};
});
expect(isChildInline({ nodeType: Node.TEXT_NODE })).toBeTruthy();
expect(isChildInline(getMockElement('div'))).toBeTruthy();
});
it('getRectTarget', () => {
expect(getRectTarget()).toBeNull();
expect(getRectTarget({ computed: false })).toBeNull();
expect(getRectTarget({ elements: [{}] })).toEqual({});
});
it('isVerticalContainer', () => {
window.getComputedStyle = jest
.fn(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'flex' : 'row';
},
};
});
expect(isVerticalContainer()).toBeFalsy();
expect(isVerticalContainer({ elements: [getMockElement('div')] })).toBeTruthy();
});
it('isVertical', () => {
expect(isVertical({ elements: [] })).toBeFalsy();
expect(isVertical({ elements: [getMockElement('div')] })).toBeFalsy();
const e1 = getMockElement('div');
const e2 = getMockElement('div');
e2.appendChild(e1);
expect(isVertical({ elements: [e1] })).toBeTruthy();
window.getComputedStyle = jest
.fn(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'inline' : 'float';
},
};
});
expect(isVertical({ elements: [getMockElement('div')] })).toBeTruthy();
});
it('getWindow', () => {
const mockElem = getMockElement('div');
expect(getWindow(mockElem)).toBe(window);
expect(getWindow(document)).toBe(window);
});

View File

@ -1,136 +0,0 @@
import '../fixtures/window';
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import { ScrollTarget, Scroller } from '../../src/designer/scroller';
import { Designer } from '../../src/designer/designer';
import {
Dragon,
} from '../../src/designer/dragon';
import formSchema from '../fixtures/schema/form';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
describe('Scroller 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let dragon: Dragon;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
beforeEach(() => {
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = project.createDocument(formSchema);
dragon = new Dragon(designer);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer.purge();
designer = null;
project = null;
dragon = null;
});
function getMockWindow() {
let scrollX = 0;
let scrollY = 0;
const mockWindow = {
scrollTo(x, y) {
if (typeof x === 'number') {
scrollX = x;
scrollY = y;
} else {
scrollX = x.left;
scrollY = x.top;
}
},
get scrollX() { return scrollX; },
get scrollY() { return scrollY; },
scrollHeight: 1000,
scrollWidth: 500,
document: {},
nodeType: Node.ELEMENT_NODE,
};
return mockWindow;
}
describe('ScrollTarget 测试', () => {
it('constructor', () => {
const win = getMockWindow();
const target = new ScrollTarget(win);
expect(target.scrollWidth).toBe(500);
expect(target.scrollHeight).toBe(1000);
target.scrollToXY(50, 50);
expect(target.left).toBe(50);
expect(target.top).toBe(50);
target.scrollTo({ left: 100, top: 100 });
expect(target.left).toBe(100);
expect(target.top).toBe(100);
});
});
function mockRAF() {
let rafCount = 0;
window.requestAnimationFrame = (fn) => {
if (rafCount++ < 2) {
fn();
} else {
window.requestAnimationFrame = () => {};
}
};
}
describe('Scroller 测试', () => {
it('scrollTarget: ScrollTarget', () => {
const win = getMockWindow();
const scrollTarget = new ScrollTarget(win);
const scroller = new Scroller({ scrollTarget, bounds: { width: 50, height: 50, top: 50, bottom: 50, left: 50, right: 50 } });
mockRAF();
scroller.scrollTo({ left: 50, top: 50 });
mockRAF();
scroller.scrolling({ globalX: 100, globalY: 100 });
});
it('scrollTarget: ScrollTarget, same left / top', () => {
const win = getMockWindow();
const scrollTarget = new ScrollTarget(win);
const scroller = new Scroller({ scrollTarget, bounds: { width: 50, height: 50, top: 50, bottom: 50, left: 50, right: 50 } });
mockRAF();
scrollTarget.scrollTo({ left: 50, top: 50 });
scroller.scrollTo({ left: 50, top: 50 });
mockRAF();
scroller.scrolling({ globalX: 100, globalY: 100 });
});
it('scrollTarget: Element', () => {
const win = getMockWindow();
// const scrollTarget = new ScrollTarget(win);
const scroller = new Scroller({ scrollTarget: win, bounds: { width: 50, height: 50, top: 50, bottom: 50, left: 50, right: 50 } });
mockRAF();
scroller.scrollTo({ left: 50, top: 50 });
mockRAF();
scroller.scrolling({ globalX: 100, globalY: 100 });
});
it('scrollTarget: null', () => {
const win = getMockWindow();
// const scrollTarget = new ScrollTarget(win);
const scroller = new Scroller({ scrollTarget: null, bounds: { width: 50, height: 50, top: 50, bottom: 50, left: 50, right: 50 } });
mockRAF();
scroller.scrollTo({ left: 50, top: 50 });
mockRAF();
scroller.scrolling({ globalX: 100, globalY: 100 });
});
});
});

View File

@ -1,79 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`setting-field 测试 纯粹的 UnitTest 常规方法 1`] = `
Object {
"extraProps": Object {
"defaultValue": "NORMAL",
"display": "inline",
},
"name": "behavior",
"setter": Object {
"componentName": "MixedSetter",
"props": Object {
"setters": Array [
Object {
"_owner": null,
"key": null,
"props": Object {
"cancelable": false,
"loose": false,
"options": Array [
Object {
"title": "普通",
"value": "NORMAL",
},
Object {
"title": "隐藏",
"value": "HIDDEN",
},
],
},
"ref": null,
},
"VariableSetter",
],
},
},
"title": "默认状态",
"type": "field",
}
`;
exports[`setting-field 测试 纯粹的 UnitTest 常规方法 2`] = `
Object {
"extraProps": Object {
"defaultValue": "NORMAL",
"display": "inline",
},
"name": "behavior",
"setter": Object {
"componentName": "MixedSetter",
"props": Object {
"setters": Array [
Object {
"_owner": null,
"key": null,
"props": Object {
"cancelable": false,
"loose": false,
"options": Array [
Object {
"title": "普通",
"value": "NORMAL",
},
Object {
"title": "隐藏",
"value": "HIDDEN",
},
],
},
"ref": null,
},
"VariableSetter",
],
},
},
"title": "默认状态",
"type": "field",
}
`;

View File

@ -1,281 +0,0 @@
// @ts-nocheck
import '../../fixtures/window';
import {
Editor,
Setters as InnerSetters,
} from '@alilc/lowcode-editor-core';
import {
Setters,
} from '@alilc/lowcode-shell';
import { SettingTopEntry } from '../../../src/designer/setting/setting-top-entry';
import { SettingField } from '../../../src/designer/setting/setting-field';
import { Node } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import settingSchema from '../../fixtures/schema/setting';
import buttonMeta from '../../fixtures/component-metadata/button';
import { DocumentModel } from 'designer/src/document';
import { delayObxTick } from '../../utils';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
const editor = new Editor();
describe('setting-field 测试', () => {
let designer: Designer;
let doc: DocumentModel;
let setters: Setters;
beforeEach(() => {
setters = new InnerSetters();
editor.set('setters', setters);
designer = new Designer({ editor, shellModelFactory });
designer.createComponentMeta(buttonMeta);
doc = designer.project.open(settingSchema);
});
afterEach(() => {
designer._componentMetasMap.clear();
designer = null;
doc.purge();
doc = null;
});
describe('纯粹的 UnitTest', () => {
let mockNode: Node;
let mockTopEntry: SettingTopEntry;
beforeEach(() => {
mockNode = new Node(designer.currentDocument, {
componentName: 'Button',
props: {
// a: 'str',
// b: 222,
// obj: {
// x: 1,
// },
// jse: {
// type: 'JSExpression',
// value: 'state.a',
// mock: 111,
// }
},
});
// mockTopEntry = new SettingTopEntry(editor, [mockNode]);
});
afterEach(() => {
mockNode = null;
mockTopEntry = null;
});
it('常规方法', () => {
// 普通 field
const settingEntry = mockNode.settingEntry;
const field = settingEntry.get('behavior');
expect(field.title).toBe('默认状态');
expect(field.expanded).toBeTruthy();
field.setExpanded(false);
expect(field.expanded).toBeFalsy();
expect(field.config).toMatchSnapshot();
expect(field.getConfig()).toMatchSnapshot();
expect(field.getConfig('extraProps')).toEqual({
display: 'inline',
defaultValue: 'NORMAL',
});
expect(field.items).toHaveLength(0);
expect(field.getItems()).toHaveLength(0);
expect(field.getItems(x => x)).toHaveLength(0);
expect(field.setter.componentName).toBe('MixedSetter');
field.purge();
expect(field.items).toHaveLength(0);
const subField = field.createField({
name: 'sub',
title: 'sub',
});
subField.setValue({
type: 'JSExpression',
value: 'state.a',
mock: 'haha',
});
subField.setHotValue('heihei');
expect(subField.getHotValue('heihei'));
expect(subField.getValue().mock).toBe('heihei');
// 不存在的 field
const nonExistingField = mockNode.settingEntry.get('non-exsiting');
expect(nonExistingField.setter).toBeNull();
// group 类型的 field
const groupField = settingEntry.get('groupkgzzeo41');
expect(groupField.items).toEqual([]);
// 有子节点的 field
const objField = settingEntry.get('obj');
expect(objField.items).toHaveLength(3);
expect(objField.getItems()).toHaveLength(3);
expect(objField.getItems(x => x.name === 'a')).toHaveLength(1);
objField.purge();
expect(objField.items).toHaveLength(0);
const objAField = settingEntry.get('obj.a');
expect(objAField.setter).toBe('StringSetter');
});
it('setValue / getValue / setHotValue / getHotValue', () => {
// 获取已有的 prop
const settingEntry = mockNode.settingEntry as SettingTopEntry;
const field = settingEntry.get('behavior');
// 会读取 extraProps.defaultValue
expect(field.getHotValue()).toBe('NORMAL');
field.setValue('HIDDEN');
expect(field.getValue()).toBe('HIDDEN');
expect(field.getHotValue()).toBe('HIDDEN');
field.setHotValue('DISABLED');
expect(field.getHotValue()).toBe('DISABLED');
field.setHotValue('NORMAL', { fromSetHotValue: true });
expect(field.getHotValue()).toBe('NORMAL');
field.setValue('HIDDEN', true);
expect(field.getHotValue()).toBe('HIDDEN');
// dirty fix list setter
field.setHotValue([{ __sid__: 1 }]);
// 数组的 field
const arrField = settingEntry.get('arr');
const subArrField = arrField.createField({
name: 0,
title: 'sub',
});
const subArrField02 = arrField.createField({
name: 1,
title: 'sub',
});
const subArrField03 = arrField.createField({
name: '2',
title: 'sub',
});
subArrField.setValue({name: '1'});
expect(subArrField.path).toEqual(['arr', 0]);
expect(subArrField02.path).toEqual(['arr', 1]);
subArrField02.setValue({name: '2'});
expect(subArrField.getValue()).toEqual({name: '1'});
expect(arrField.getHotValue()).toEqual([{name: '1'}, {name: '2'}]);
subArrField.clearValue();
expect(subArrField.getValue()).toBeUndefined();
expect(arrField.getHotValue()).toEqual([undefined, {name: '2'}]);
subArrField03.setValue({name: '3'});
expect(arrField.getHotValue()).toEqual([undefined, {name: '2'}, {name: '3'}]);
});
it('js expression setValue / setHotValue', () => {
const settingEntry = mockNode.settingEntry;
const field = settingEntry.get('behavior');
const subField = field.createField({
name: 'sub',
title: 'sub',
});
subField.setValue({
type: 'JSExpression',
value: 'state.a',
mock: 'haha',
});
subField.setHotValue({
type: 'JSExpression',
value: 'state.b',
});
expect(subField.getValue()).toEqual({
type: 'JSExpression',
value: 'state.b',
mock: 'haha',
});
subField.setHotValue('mock02');
expect(subField.getValue()).toEqual({
type: 'JSExpression',
value: 'state.b',
mock: 'mock02',
});
});
it('onEffect', async () => {
const settingEntry = mockNode.settingEntry as SettingTopEntry;
const field = settingEntry.get('behavior');
const mockFn = jest.fn();
field.onEffect(mockFn);
field.setValue('DISABLED');
await delayObxTick();
expect(mockFn).toHaveBeenCalled();
});
it('autorun', async () => {
const settingEntry = mockNode.settingEntry as SettingTopEntry;
const arrField = settingEntry.get('columns');
const subArrField = arrField.createField({
name: 0,
title: 'sub',
});
const objSubField = subArrField.createField({
name: 'objSub',
title: 'objSub',
});
const mockFnArrField = jest.fn();
const mockFnSubArrField = jest.fn();
const mockFnObjSubField = jest.fn();
arrField.setValue([{ objSub: "subMock0.Index.0" }]);
// 这里需要 setValue 两遍,触发 prop 的 purge 方法,使 purged 为 true之后的 purge 方法不会正常执行prop 才能正常缓存autorun 才能正常执行
// TODO: 该机制后续得研究一下,再确定是否要修改
arrField.setValue([{ objSub: "subMock0.Index.0" }]);
arrField.onEffect(() => {
mockFnArrField(arrField.getValue());
});
arrField.onEffect(() => {
mockFnSubArrField(subArrField.getValue());
});
arrField.onEffect(() => {
mockFnObjSubField(objSubField.getValue());
});
await delayObxTick();
expect(mockFnObjSubField).toHaveBeenCalledWith('subMock0.Index.0');
expect(mockFnSubArrField).toHaveBeenCalledWith({ objSub: "subMock0.Index.0" });
expect(mockFnArrField).toHaveBeenCalledWith([{ objSub: "subMock0.Index.0" }]);
arrField.setValue([{ objSub: "subMock0.Index.1" }]);
await delayObxTick();
expect(mockFnObjSubField).toHaveBeenCalledWith('subMock0.Index.1');
expect(mockFnSubArrField).toHaveBeenCalledWith({ objSub: "subMock0.Index.1" });
expect(mockFnArrField).toHaveBeenCalledWith([{ objSub: "subMock0.Index.1" }]);
subArrField.setValue({ objSub: "subMock0.Index.2" });
await delayObxTick();
expect(mockFnObjSubField).toHaveBeenCalledWith('subMock0.Index.2');
expect(mockFnSubArrField).toHaveBeenCalledWith({ objSub: "subMock0.Index.2" });
expect(mockFnArrField).toHaveBeenCalledWith([{ objSub: "subMock0.Index.2" }]);
objSubField.setValue('subMock0.Index.3');
await delayObxTick();
expect(mockFnObjSubField).toHaveBeenCalledWith('subMock0.Index.3');
expect(mockFnSubArrField).toHaveBeenCalledWith({ objSub: "subMock0.Index.3" });
expect(mockFnArrField).toHaveBeenCalledWith([{ objSub: "subMock0.Index.3" }]);
})
});
});

View File

@ -1,230 +0,0 @@
import '../../fixtures/window';
import {
Editor,
Setters as InnerSetters,
} from '@alilc/lowcode-editor-core';
import { SettingTopEntry } from '../../../src/designer/setting/setting-top-entry';
import { SettingPropEntry } from '../../../src/designer/setting/setting-prop-entry';
import { Node } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import settingSchema from '../../fixtures/schema/setting';
import divMeta from '../../fixtures/component-metadata/div';
import { DocumentModel } from 'designer/src/document';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
const editor = new Editor();
describe('setting-prop-entry 测试', () => {
let designer: Designer;
let doc: DocumentModel;
let setters: any;
beforeEach(() => {
setters = new InnerSetters();
editor.set('setters', setters);
designer = new Designer({ editor, shellModelFactory });
designer.createComponentMeta(divMeta);
doc = designer.project.open(settingSchema);
});
afterEach(() => {
designer._componentMetasMap.clear();
designer = null;
doc.purge();
doc = null;
});
describe('纯粹的 UnitTest', () => {
let mockNode: Node;
let mockTopEntry: SettingTopEntry;
beforeEach(() => {
mockNode = new Node(designer.currentDocument, {
componentName: 'Button',
props: {
a: 'str',
b: 222,
obj: {
x: 1,
},
jse: {
type: 'JSExpression',
value: 'state.a',
mock: 111,
}
},
});
mockTopEntry = new SettingTopEntry(editor, [mockNode]);
});
afterEach(() => {
mockNode = null;
mockTopEntry = null;
});
it('常规方法', () => {
// type: group 类型
const prop = new SettingPropEntry(mockTopEntry, 'xGroup', 'group');
expect(prop.setKey('xxx')).toBeUndefined();
expect(prop.remove()).toBeUndefined();
const prop2 = new SettingPropEntry(mockTopEntry, '#xGroup');
expect(prop2.setKey('xxx')).toBeUndefined();
expect(prop2.remove()).toBeUndefined();
expect(prop.getVariableValue()).toBe('');
});
it('setValue / getValue / onValueChange', () => {
// 获取已有的 prop
const prop1 = mockTopEntry.getProp('a');
prop1.extraProps = {
getValue: (prop, val) => `prefix ${val}`,
// prop 是 shell prop entry
setValue: (prop, val) => { prop.setValue(`modified ${val}`, { disableMutator: true }) },
defaultValue: 'default',
};
expect(prop1.getDefaultValue()).toBe('default');
expect(prop1.getValue()).toBe('prefix str');
// disableMutator: true
prop1.setValue('bbb', false, false, { disableMutator: true });
expect(prop1.getValue()).toBe('prefix bbb');
// disableMutator: false
prop1.setValue('bbb');
expect(prop1.getValue()).toBe('prefix modified bbb');
const mockFn3 = jest.fn();
const prop2 = mockTopEntry.getProp('obj');
const prop3 = prop2.get('x');
const offFn = prop3.onValueChange(mockFn3);
expect(prop3.getValue()).toBe(1);
prop3.setValue(2);
expect(mockFn3).toHaveBeenCalled();
offFn();
prop3.setValue(3);
mockFn3.mockClear();
expect(mockFn3).toHaveBeenCalledTimes(0);
const prop4 = mockTopEntry.getProp('b');
prop4.extraProps = {
getValue: () => { throw 'error'; },
};
expect(prop4.getValue()).toBe(222);
});
it('clearValue', () => {
const prop1 = mockTopEntry.getProp('a');
prop1.clearValue();
expect(prop1.getValue()).toBeUndefined();
const mockFn = jest.fn();
prop1.extraProps = {
setValue: mockFn,
};
prop1.clearValue();
expect(mockFn).toHaveBeenCalled();
});
it('getVariableValue/ setUseVariable / isUseVariable / getMockOrValue', () => {
const prop1 = mockTopEntry.getProp('jse');
expect(prop1.isUseVariable()).toBeTruthy();
expect(prop1.useVariable).toBeTruthy();
expect(prop1.getMockOrValue()).toEqual(111);
expect(prop1.getVariableValue()).toEqual('state.a');
prop1.setUseVariable(false);
expect(prop1.getValue()).toEqual(111);
prop1.setUseVariable(true);
expect(prop1.getValue()).toEqual({
type: 'JSExpression',
value: '',
mock: 111,
});
prop1.setUseVariable(true);
});
});
describe('node 构造函数生成 settingEntry', () => {
it('常规方法测试', () => {
const divNode = doc?.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.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('setValue / getValue', () => {
const divNode = doc?.getNode('div');
const { settingEntry } = divNode!;
const behaviorProp = settingEntry.getProp('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(behaviorProp.getValue()).toBeUndefined();
behaviorProp.setValue('LARGE');
expect(behaviorProp.getValue()).toBe('LARGE');
behaviorProp.remove();
expect(divNode?.getProp('behavior').getValue()).toBeUndefined();
});
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',
});
});
});
});

View File

@ -1,196 +0,0 @@
import '../../fixtures/window';
import { Editor, Setters } from '@alilc/lowcode-editor-core';
import { Node } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import settingSchema from '../../fixtures/schema/setting';
import divMeta from '../../fixtures/component-metadata/div';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
const editor = new Editor();
describe('setting-top-entry 测试', () => {
let designer: Designer;
beforeEach(() => {
editor.set('setters', new Setters())
designer = new Designer({ editor, shellModelFactory });
});
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('onMetadataChange', () => {
designer.createComponentMeta(divMeta);
designer.project.open(settingSchema);
const { currentDocument } = designer.project;
const divNode = currentDocument?.getNode('div') as Node;
const { settingEntry } = divNode!;
const mockFn = jest.fn();
settingEntry.componentMeta.onMetadataChange(mockFn);
settingEntry.componentMeta.refreshMetadata();
expect(mockFn).toHaveBeenCalled();
});
it.skip('setupItems - customView', () => {
designer.createComponentMeta(divMeta);
designer.project.open(settingSchema);
const { currentDocument } = designer.project;
const divNode = currentDocument?.getNode('div') as Node;
const { settingEntry } = divNode;
// 模拟将第一个配置变成 react funcional component
settingEntry.componentMeta.getMetadata().combined[0].items[0] = props => props.xx;
settingEntry.setupItems();
});
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 强行调用,是否需要做健壮性保护?
});
});
});

View File

@ -1,313 +0,0 @@
import '../../fixtures/window';
import { DocumentModel, isDocumentModel, isPageSchema } from '../../../src/document/document-model';
import { Editor } from '@alilc/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import divMeta from '../../fixtures/component-metadata/div';
import formMeta from '../../fixtures/component-metadata/form';
import otherMeta from '../../fixtures/component-metadata/other';
import pageMeta from '../../fixtures/component-metadata/page';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
describe('document-model 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
});
it('empty schema', () => {
const doc = new DocumentModel(project);
expect(doc.rootNode?.id).toBe('root');
expect(doc.currentRoot).toBe(doc.rootNode);
expect(doc.root).toBe(doc.rootNode);
expect(doc.modalNode).toBeUndefined();
expect(doc.isBlank()).toBeTruthy();
expect(doc.schema).toEqual({
componentName: 'Page',
condition: true,
conditionGroup: '',
hidden: false,
isLocked: false,
loop: undefined,
title: '',
id: 'root',
fileName: '',
props: {},
});
});
it('各种方法测试', () => {
const doc = new DocumentModel(project, formSchema);
const mockNode = { id: 1 };
doc.addWillPurge(mockNode);
expect(doc.willPurgeSpace).toHaveLength(1);
doc.removeWillPurge(mockNode);
expect(doc.willPurgeSpace).toHaveLength(0);
expect(doc.toData()).toMatchSnapshot();
// 测试插入已存在的 idid 将会被重置
const formParentNode = doc.getNode('form').parent;
doc.insertNode(formParentNode, { id: 'form', componentName: 'Form' });
expect(formParentNode.children.get(formParentNode.children.size - 1).id).not.toBe('form');
doc.internalRemoveAndPurgeNode({ id: 'mockId' });
// internalSetDropLocation
doc.dropLocation = { a: 1 };
expect(doc.dropLocation).toEqual({ a: 1 });
// wrapWith
// none-selected
doc.wrapWith({ componentName: 'Wrap' });
doc.selection.select('form');
doc.wrapWith({ componentName: 'Wrap' });
expect(doc.getNode('form').parent.componentName).toBe('Wrap');
expect(doc.wrapWith({ componentName: 'Leaf' })).toBeNull();
// fileName
expect(doc.fileName).toBeTruthy();
doc.fileName = 'fileName';
expect(doc.fileName).toBe('fileName');
expect(doc.getNodeSchema(doc.getNode('form'))).toMatchSnapshot();
// TODO:
// expect(doc.simulatorProps).toMatchSnapshot();
const mockSimulator = {
isSimulator: true,
getComponent() {
return 'haha';
},
setSuspense() {},
};
doc.project.mountSimulator(mockSimulator);
expect(doc.simulator).toEqual(mockSimulator);
expect(doc.getComponent('Div')).toBe('haha');
expect(doc.opened).toBeFalsy();
expect(doc.isModified).toBeTruthy();
expect(doc.suspensed).toBeTruthy();
doc.open();
expect(doc.opened).toBeTruthy();
expect(doc.actived).toBeTruthy();
expect(doc.isModified).toBeTruthy();
expect(doc.suspensed).toBeFalsy();
doc.suspense();
doc.activate();
doc.close();
doc.remove();
const offReady = doc.onReady(() => {});
offReady();
expect(doc.history).toBe(doc.getHistory());
});
it('focusNode - using drillDown', () => {
const doc = new DocumentModel(project, formSchema);
expect(doc.focusNode.id).toBe('page');
doc.drillDown(doc.getNode('node_k1ow3cbb'));
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
});
it('focusNode - using drillDown & import', () => {
const doc = new DocumentModel(project, formSchema);
expect(doc.focusNode.id).toBe('page');
doc.drillDown(doc.getNode('node_k1ow3cbb'));
doc.import(formSchema);
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
});
it('focusNode - using focusNodeSelector', () => {
const doc = new DocumentModel(project, formSchema);
editor.set('focusNodeSelector', (rootNode) => {
return rootNode.children.get(1);
});
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
});
it('getNodeCount', () => {
const doc = new DocumentModel(project);
// using default schema, only one node
expect(doc.getNodeCount()).toBe(1);
});
it('getNodeSchema', () => {
const doc = new DocumentModel(project, formSchema);
expect(doc.getNodeSchema('page').id).toBe('page');
});
it('export - with __isTopFixed__', () => {
formSchema.children[1].props.__isTopFixed__ = true;
const doc = new DocumentModel(project, formSchema);
const schema = doc.export();
expect(schema.children).toHaveLength(3);
expect(schema.children[0].componentName).toBe('RootContent');
expect(schema.children[1].componentName).toBe('RootHeader');
expect(schema.children[2].componentName).toBe('RootFooter');
});
describe('createNode', () => {
it('same id && componentName', () => {
const doc = new DocumentModel(project, formSchema);
const node = doc.createNode({
componentName: 'RootFooter',
id: 'node_k1ow3cbc',
props: {},
condition: true,
});
expect(node.parent).toBeNull();
});
it('same id && different componentName', () => {
const doc = new DocumentModel(project, formSchema);
const originalNode = doc.getNode('node_k1ow3cbc');
const node = doc.createNode({
componentName: 'RootFooter2',
id: 'node_k1ow3cbc',
props: {},
condition: true,
});
// expect(originalNode.parent).toBeNull();
expect(node.id).not.toBe('node_k1ow3cbc');
});
});
it('setSuspense', () => {
const doc = new DocumentModel(project, formSchema);
expect(doc.opened).toBeFalsy();
doc.setSuspense(false);
});
it('registerAddon / getAddonData / exportAddonData', () => {
const doc = new DocumentModel(project);
expect(doc.getAddonData('a')).toBeUndefined();
doc.registerAddon('a', () => 'addon a');
doc.registerAddon('a', () => 'modified addon a');
doc.registerAddon('b', () => 'addon b');
doc.registerAddon('c', () => null);
['id', 'layout', 'params'].forEach((name) => {
expect(() => doc.registerAddon(name, () => {})).toThrow();
});
expect(doc.getAddonData('a')).toBe('modified addon a');
expect(doc.getAddonData('b')).toBe('addon b');
expect(doc.exportAddonData()).toEqual({
a: 'modified addon a',
b: 'addon b',
});
});
it('checkNesting / checkDropTarget / checkNestingUp / checkNestingDown', () => {
designer.createComponentMeta(pageMeta);
designer.createComponentMeta(formMeta);
designer.createComponentMeta(otherMeta);
const doc = new DocumentModel(project, formSchema);
expect(
doc.checkDropTarget(doc.getNode('page'), { type: 'node', nodes: [doc.getNode('form')] }),
).toBeTruthy();
expect(
doc.checkDropTarget(doc.getNode('page'), {
type: 'nodedata',
data: { componentName: 'Form' },
}),
).toBeTruthy();
expect(
doc.checkNesting(doc.getNode('page'), { type: 'node', nodes: [doc.getNode('form')] }),
).toBeTruthy();
expect(
doc.checkNesting(doc.getNode('page'), {
type: 'nodedata',
data: { componentName: 'Form' },
}),
).toBeTruthy();
expect(
doc.checkNesting(doc.getNode('page'), doc.getNode('form'))
).toBeTruthy();
expect(
doc.checkNesting(doc.getNode('page'), null)
).toBeTruthy();
expect(
doc.checkNesting(doc.getNode('page'), {
type: 'nodedata',
data: { componentName: 'Other' },
})
).toBeFalsy();
expect(
doc.checkNestingUp(doc.getNode('page'), { componentName: 'Other' })
).toBeFalsy();
expect(
doc.checkNestingDown(doc.getNode('page'), { componentName: 'Other' })
).toBeTruthy();
expect(doc.checkNestingUp(doc.getNode('page'), null)).toBeTruthy();
});
it('getComponentsMap', () => {
designer.createComponentMeta(divMeta);
designer.createComponentMeta(otherMeta);
const doc = new DocumentModel(project, formSchema);
const comps = doc.getComponentsMap(['Other']);
expect(comps.find(comp => comp.componentName === 'Div')).toEqual(
{ componentName: 'Div', package: '@ali/vc-div' }
);
expect(comps.find(comp => comp.componentName === 'Other')).toEqual(
{ componentName: 'Other', package: '@ali/vc-other' }
);
expect(comps.find(comp => comp.componentName === 'Page')).toEqual(
{ componentName: 'Page', devMode: 'lowCode' }
);
const comps2 = doc.getComponentsMap(['Div']);
});
it('acceptRootNodeVisitor / getRootNodeVisitor', () => {
designer.createComponentMeta(divMeta);
designer.createComponentMeta(otherMeta);
const doc = new DocumentModel(project, formSchema);
const ret = doc.acceptRootNodeVisitor('getPageId', (root) => {
return 'page';
});
expect(ret).toBe('page');
expect(doc.getRootNodeVisitor('getPageId')).toBe('page');
// expect(doc.getComponentsMap(['Other'])).toEqual([
// { componentName: 'Div', package: '@ali/vc-div' },
// { componentName: 'Other', package: '@ali/vc-other' },
// ]);
});
it('deprecated methods', () => {
const doc = new DocumentModel(project, formSchema);
doc.refresh();
doc.onRefresh();
});
});
it('isDocumentModel', () => {
expect(isDocumentModel({ rootNode: {} })).toBeTruthy();
});
it('isPageSchema', () => {
expect(isPageSchema({ componentName: 'Page' })).toBeTruthy();
});

View File

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`History data function & records 1`] = `"{\\"data\\":1,\\"children\\":[{\\"data\\":2,\\"children\\":[]}]}"`;
exports[`History data function & records 2`] = `"{\\"data\\":3,\\"children\\":[{\\"data\\":2,\\"children\\":[]}]}"`;
exports[`History data function & records 3`] = `"{\\"data\\":5,\\"children\\":[{\\"data\\":2,\\"children\\":[]}]}"`;
exports[`History data function & records 4`] = `"{\\"data\\":7,\\"children\\":[{\\"data\\":2,\\"children\\":[]}]}"`;

View File

@ -1,343 +0,0 @@
import '../../fixtures/window';
import { mobx, makeAutoObservable, globalContext, Editor } from '@alilc/lowcode-editor-core';
import { History } from '../../../src/document/history';
import { delay } from '../../utils/misc';
import { Workspace } from '@alilc/lowcode-workspace';
class Node {
data: number;
children: Node[] = [];
constructor(data: number) {
makeAutoObservable(this);
this.data = data;
}
addNode(node: Node) {
this.children.push(node);
}
toObject() {
return {
data: this.data,
children: this.children.map((c) => c.toObject()),
};
}
}
let tree: Node = null;
beforeEach(() => {
tree = new Node(1);
tree.addNode(new Node(2));
});
afterEach(() => {
tree = null;
});
describe('History', () => {
beforeAll(() => {
const editor = new Editor();
globalContext.register(editor, Editor);
globalContext.register(editor, 'editor');
globalContext.register(new Workspace(), 'workspace');
});
it('data function & records', async () => {
const mockRedoFn = jest.fn();
const mockDataFn = jest.fn();
const history = new History<Node>(() => {
const data = tree.toObject();
mockDataFn(data);
return data;
}, mockRedoFn);
expect(mockDataFn).toHaveBeenCalledTimes(1);
expect(mockDataFn).toHaveBeenCalledWith({ data: 1, children: [{ data: 2, children: [] }] });
expect(history.hotData).toMatchSnapshot();
// @ts-ignore
expect(history.session.cursor).toBe(0);
// @ts-ignore
expect(history.records).toHaveLength(1);
tree.data = 3;
expect(mockDataFn).toHaveBeenCalledTimes(2);
expect(mockDataFn).toHaveBeenCalledWith({ data: 3, children: [{ data: 2, children: [] }] });
expect(history.hotData).toMatchSnapshot();
// @ts-ignore
expect(history.session.cursor).toBe(0);
// @ts-ignore
expect(history.records).toHaveLength(1);
// modify data after timeGap
await delay(1200);
tree.data = 5;
expect(mockDataFn).toHaveBeenCalledTimes(3);
expect(mockDataFn).toHaveBeenCalledWith({ data: 5, children: [{ data: 2, children: [] }] });
expect(history.hotData).toMatchSnapshot();
// @ts-ignore
expect(history.session.cursor).toBe(1);
// @ts-ignore
expect(history.records).toHaveLength(2);
history.setSerialization({
serialize(data: Node): string {
return JSON.stringify(data);
},
unserialize(data: string) {
return JSON.parse(data);
},
});
// modify data after timeGap
await delay(1200);
tree.data = 7;
expect(mockDataFn).toHaveBeenCalledTimes(4);
expect(mockDataFn).toHaveBeenCalledWith({ data: 7, children: [{ data: 2, children: [] }] });
expect(history.hotData).toMatchSnapshot();
});
it('isSavePoint & savePoint', async () => {
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
() => {},
);
expect(history.isSavePoint()).toBeFalsy();
expect(history.isModified()).toBeFalsy();
await delay(1200);
tree.data = 3;
expect(history.isSavePoint()).toBeTruthy();
history.savePoint();
expect(history.isSavePoint()).toBeFalsy();
});
it('go & forward & back & onCursor', async () => {
const mockRedoFn = jest.fn();
const mockCursorFn = jest.fn();
const mockStateFn = jest.fn();
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
mockRedoFn(data);
},
);
// undoable ❌ & redoable ❌ & modified ❌
expect(history.getState()).toBe(0);
await delay(1200);
tree.data = 3;
await delay(1200);
tree.data = 5;
await delay(1200);
tree.data = 7;
const dataCursor0 = { data: 1, children: [{ data: 2, children: [] }] };
const dataCursor1 = { data: 3, children: [{ data: 2, children: [] }] };
const dataCursor2 = { data: 5, children: [{ data: 2, children: [] }] };
const dataCursor3 = { data: 7, children: [{ data: 2, children: [] }] };
// redoable ❌
expect(history.getState()).toBe(7 - 2);
const off1 = history.onCursor(mockCursorFn);
const off2 = history.onStateChange(mockStateFn);
// @ts-ignore
expect(history.records).toHaveLength(4);
// @ts-ignore
expect(history.session.cursor).toBe(3);
// step 1
history.back();
expect(mockCursorFn).toHaveBeenNthCalledWith(
1,
JSON.stringify(dataCursor2),
);
expect(mockStateFn).toHaveBeenNthCalledWith(1, 7);
expect(mockRedoFn).toHaveBeenNthCalledWith(1, dataCursor2);
// step 2
history.back();
expect(mockCursorFn).toHaveBeenNthCalledWith(
2,
JSON.stringify(dataCursor1),
);
expect(mockStateFn).toHaveBeenNthCalledWith(2, 7);
expect(mockRedoFn).toHaveBeenNthCalledWith(2, dataCursor1);
// step 3
history.back();
expect(mockCursorFn).toHaveBeenNthCalledWith(
3,
JSON.stringify(dataCursor0),
);
expect(mockStateFn).toHaveBeenNthCalledWith(3, 7 - 4 - 1);
expect(mockRedoFn).toHaveBeenNthCalledWith(3, dataCursor0);
// step 4
history.forward();
expect(mockCursorFn).toHaveBeenNthCalledWith(
4,
JSON.stringify(dataCursor1),
);
expect(mockStateFn).toHaveBeenNthCalledWith(4, 7);
expect(mockRedoFn).toHaveBeenNthCalledWith(4, dataCursor1);
// step 5
history.forward();
expect(mockCursorFn).toHaveBeenNthCalledWith(
5,
JSON.stringify(dataCursor2),
);
expect(mockStateFn).toHaveBeenNthCalledWith(5, 7);
expect(mockRedoFn).toHaveBeenNthCalledWith(5, dataCursor2);
// step 6
history.go(3);
expect(mockCursorFn).toHaveBeenNthCalledWith(
6,
JSON.stringify(dataCursor3),
);
expect(mockStateFn).toHaveBeenNthCalledWith(6, 7 - 2);
expect(mockRedoFn).toHaveBeenNthCalledWith(6, dataCursor3);
// step 7
history.go(0);
expect(mockCursorFn).toHaveBeenNthCalledWith(
7,
JSON.stringify(dataCursor0),
);
expect(mockStateFn).toHaveBeenNthCalledWith(7, 7 - 4 - 1);
expect(mockRedoFn).toHaveBeenNthCalledWith(7, dataCursor0);
off1();
off2();
mockStateFn.mockClear();
mockCursorFn.mockClear();
history.go(1);
expect(mockStateFn).not.toHaveBeenCalled();
expect(mockCursorFn).not.toHaveBeenCalled();
});
it('go() - edge case of cursor', async () => {
const mockRedoFn = jest.fn();
const mockCursorFn = jest.fn();
const mockStateFn = jest.fn();
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
mockRedoFn(data);
},
);
await delay(1200);
tree.data = 3;
await delay(1200);
tree.data = 5;
history.go(-1);
// @ts-ignore
expect(history.session.cursor).toBe(0);
history.go(3);
// @ts-ignore
expect(history.session.cursor).toBe(2);
});
it('destroy()', async () => {
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
mockRedoFn(data);
},
);
history.destroy();
// @ts-ignore
expect(history.records).toHaveLength(0);
});
it('sleep & wakeup', async () => {
const mockRedoFn = jest.fn();
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
mockRedoFn(data);
},
);
// @ts-ignore
history.sleep();
await delay(1200);
tree.data = 3;
// no record has been pushed into records because of history is asleep.
// @ts-ignore
expect(history.records).toHaveLength(1);
// @ts-ignore
history.wakeup();
tree.data = 4;
// @ts-ignore
expect(history.records).toHaveLength(2);
});
});
describe('History - errors', () => {
beforeAll(() => {
globalContext.replace(Editor, null);
});
it('no editor', () => {
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
},
);
history.back();
history.forward();
});
it('no session', () => {
const history = new History<Node>(
() => {
const data = tree.toObject();
return data;
},
(data) => {
},
);
// @ts-ignore
history.session = undefined;
history.back();
history.forward();
history.savePoint();
});
});

View File

@ -1,57 +0,0 @@
import '../../fixtures/window';
import { Session } from '../../../src/document/history';
import { delay } from '../../utils/misc';
describe('Session', () => {
it('constructor', () => {
const session = new Session(1, { a: 1 });
expect(session.cursor).toBe(1);
expect(session.data).toEqual({ a: 1 });
// @ts-ignore
expect(session.timeGap).toBe(1000);
expect(session.isActive()).toBeTruthy();
});
it('log()', () => {
const session = new Session(1, { a: 1 });
session.log({ a: 2 });
session.log({ a: 3 });
expect(session.data).toEqual({ a: 3 });
});
it('end()', () => {
const session = new Session(1, { a: 1 });
session.end();
expect(session.isActive()).toBeFalsy();
session.log({ a: 2 });
// log is not possible if current session is inactive
expect(session.data).toEqual({ a: 1 });
});
it('timeGap', async () => {
const session = new Session(1, { a: 1 });
expect(session.isActive()).toBeTruthy();
await delay(1200);
expect(session.isActive()).toBeFalsy();
session.log({ a: 2 });
// log is not possible if current session is inactive
expect(session.data).toEqual({ a: 1 });
});
it('custom timeGap', async () => {
const session = new Session(1, { a: 1 }, 2000);
expect(session.isActive()).toBeTruthy();
await delay(1200);
expect(session.isActive()).toBeTruthy();
await delay(1000);
expect(session.isActive()).toBeFalsy();
session.log({ a: 2 });
// log is not possible if current session is inactive
expect(session.data).toEqual({ a: 1 });
});
});

View File

@ -1,112 +0,0 @@
import '../../fixtures/window';
import { Editor } from '@alilc/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { DocumentModel } from '../../../src/document/document-model';
import { Node } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form-with-modal';
import dlgMetadata from '../../fixtures/component-metadata/dialog';
import { getModalNodes } from '../../../src/document/node/modal-nodes-manager';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
designer.createComponentMeta(dlgMetadata);
project = designer.project;
doc = new DocumentModel(project, formSchema);
});
afterEach(() => {
project.unload();
designer.purge();
editor = null;
designer = null;
project = null;
});
describe('ModalNodesManager 方法测试', () => {
it('getModalNodes / getVisibleModalNode', () => {
const mgr = doc.modalNodesManager;
const nodes = mgr.getModalNodes();
expect(nodes).toHaveLength(1);
expect(nodes[0].componentName).toBe('Dialog');
expect(mgr.getVisibleModalNode()).toBeFalsy();
});
it('setVisible / setInvisible / onVisibleChange', () => {
const mgr = doc.modalNodesManager;
const nodes = mgr.getModalNodes();
const mockFn = jest.fn();
const off = mgr.onVisibleChange(mockFn);
mgr.setVisible(nodes[0]);
expect(mockFn).toHaveBeenCalledTimes(2);
mgr.setInvisible(nodes[0]);
expect(mockFn).toHaveBeenCalledTimes(3);
off();
});
it('addNode / removeNode', () => {
const mgr = doc.modalNodesManager!;
const nodes = mgr.getModalNodes();
const nodesMockFn = jest.fn();
const visibleMockFn = jest.fn();
const off = mgr.onModalNodesChange(nodesMockFn);
const offVisible = mgr.onVisibleChange(visibleMockFn);
const newNode = new Node(doc, { componentName: 'Dialog' });
mgr.addNode(newNode);
expect(visibleMockFn).toHaveBeenCalledTimes(2);
expect(nodesMockFn).toHaveBeenCalledTimes(1);
mgr.setVisible(newNode);
mgr.removeNode(newNode);
expect(visibleMockFn).toHaveBeenCalledTimes(6);
expect(nodesMockFn).toHaveBeenCalledTimes(2);
off();
offVisible();
visibleMockFn.mockClear();
nodesMockFn.mockClear();
mgr.addNode(newNode);
expect(visibleMockFn).not.toHaveBeenCalled();
expect(nodesMockFn).not.toHaveBeenCalled();
const newNode2 = new Node(doc, { componentName: 'Dialog' });
mgr.addNode(newNode2);
mgr.setInvisible(newNode2);
mgr.removeNode(newNode2);
const newNode3 = new Node(doc, { componentName: 'Dialog' });
mgr.removeNode(newNode3);
const newNode4 = new Node(doc, { componentName: 'Non-Modal' });
mgr.removeNode(newNode4);
const newNode5 = doc.createNode({ componentName: 'Non-Modal' });
newNode5.remove(); // trigger node destroy
});
});
describe('其他方法', () => {
it('getModalNodes - null', () => {
expect(getModalNodes()).toEqual([]);
});
it('getModalNodes - no children', () => {
const node = doc.createNode({ componentName: 'Leaf', children: 'haha' });
expect(getModalNodes(node)).toEqual([]);
});
});

View File

@ -1,250 +0,0 @@
import '../../fixtures/window';
import { Editor } from '@alilc/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { DocumentModel } from '../../../src/document/document-model';
import {
Node,
} from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import divMetadata from '../../fixtures/component-metadata/div';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
describe('NodeChildren 方法测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = new DocumentModel(project, formSchema);
});
afterEach(() => {
project.unload();
designer.purge();
editor = null;
designer = null;
project = null;
});
it('isEmpty / notEmpty', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const secondBtn = doc.getNode('node_k1ow3cbp')!;
const { children } = firstBtn.parent!;
expect(children.isEmpty()).toBeFalsy();
expect(children.notEmpty()).toBeTruthy();
expect(firstBtn.children.notEmpty()).toBeFalsy();
expect(firstBtn.children.isEmpty()).toBeTruthy();
});
it('export', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
expect(children.export().length).toBe(2);
});
it('export - Leaf', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.parent!.insertAfter({ componentName: 'Leaf', children: 'haha' });
const { children } = firstBtn.parent!;
expect(children.export().length).toBe(3);
expect(children.export()[2]).toBe('haha');
});
it('import', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.import(children.export());
expect(children.export().length).toBe(2);
});
it('delete', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const leafNode = doc.createNode({ componentName: 'Leaf', children: 'haha' });
firstBtn.parent!.insertAfter(leafNode);
const { children } = firstBtn.parent!;
children.delete(leafNode);
expect(children.export().length).toBe(2);
});
it('delete - 插入已有的节点', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.parent!.insertBefore(firstBtn, firstBtn);
const { children } = firstBtn.parent!;
expect(children.export().length).toBe(2);
});
it('purge / for of', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.purge();
for (const child of children) {
expect(child.isPurged).toBeTruthy();
}
// purge when children is purged
children.purge();
});
it('splice', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.splice(0, 1);
expect(children.size).toBe(1);
expect(children.length).toBe(1);
children.splice(0, 0, doc.createNode({ componentName: 'Button' }));
expect(children.size).toBe(2);
expect(children.length).toBe(2);
});
it('map', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const newMap = children.map((item) => item);
newMap?.forEach((item) => {
expect(item.componentName).toBe('Button');
});
});
it('forEach', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.forEach((item) => {
expect(item.componentName).toBe('Button');
});
});
it('some', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.some((item) => {
return expect(item.componentName).toBe('Button');
});
});
it('every', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children.every((item) => {
return expect(item.componentName).toBe('Button');
});
});
it('filter', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
children
.filter((item) => item.componentName === 'Button')
.forEach((item) => {
expect(item.componentName).toBe('Button');
});
});
it('find', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const found = children.find((item) => item.componentName === 'Button');
expect(found?.componentName).toBe('Button');
});
it('concat', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const ret = children.concat([doc.createNode({ componentName: 'Button' })]);
expect(ret.length).toBe(3);
});
it('reduce', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
let ret = 0;
ret = children.reduce((count, node) => {
count = count + 1;
return count;
}, 0);
expect(ret).toBe(2);
});
it('mergeChildren', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const changeMockFn = jest.fn();
const offChange = children.onChange(changeMockFn);
const rmMockFn = jest.fn((item) => {
if (item.index === 1) return true;
return false;
});
const addMockFn = jest.fn((children) => {
return [{ componentName: 'Button' }, { componentName: 'Button' }];
});
const sortMockFn = jest.fn((a, b) => {
return a > b ? 1 : -1;
});
children.mergeChildren(rmMockFn, addMockFn, sortMockFn);
expect(children.size).toBe(3);
expect(changeMockFn).toHaveBeenCalled();
offChange();
// no remover && adder && sorter
children.mergeChildren();
});
it('insert / onInsert', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const mockFn = jest.fn();
const off = children.onInsert(mockFn);
children.insert(new Node(doc, { componentName: 'Button' }));
expect(mockFn).toHaveBeenCalledTimes(1);
off();
children.insert(new Node(doc, { componentName: 'Button' }));
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('reportModified', () => {
const divMeta = designer.createComponentMeta(divMetadata);
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const { children } = firstBtn.parent!;
const modifiedMockFn = jest.fn();
divMeta.getMetadata = () => {
return { configure: { advanced: { callbacks: { onSubtreeModified: modifiedMockFn } } } };
};
children.reportModified(null);
children.reportModified(doc.rootNode);
children.reportModified(firstBtn, firstBtn.parent);
expect(modifiedMockFn).toHaveBeenCalled();
});
});

View File

@ -1,588 +0,0 @@
import { set, cloneDeep } from 'lodash-es';
import '../../fixtures/window';
import { Project, IProject } from '../../../src/project/project';
import { Node, INode } 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 { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) {
return props;
},
createSettingEntry: mockCreateSettingEntry,
postEvent() {},
};
}),
};
});
let designer = null;
beforeAll(() => {
designer = new Designer({});
});
describe('schema 生成节点模型测试', () => {
describe('block ❌ | component ❌ | slot ❌', () => {
let project: IProject;
beforeEach(() => {
project = new Project(designer, {
componentsTree: [formSchema],
});
project.open();
});
afterEach(() => {
project.unload();
});
it('基本的节点模型初始化,模型导出', () => {
expect(project).toBeTruthy();
const { currentDocument } = project;
const nodesMap = currentDocument?.nodesMap;
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('page');
expect(pageNode?.getComponentName()).toBe('Page');
expect(pageNode?.getIcon()).toBeUndefined();
const exportSchema = currentDocument?.export(1);
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
nodesMap.forEach((node) => {
// 触发 getter
node.settingEntry;
});
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
});
it('基本的节点模型初始化,节点深度', () => {
expect(project).toBeTruthy();
const { currentDocument } = project;
const getNode = currentDocument?.getNode.bind(currentDocument);
const pageNode = getNode?.('page');
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('page');
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?.('page');
const nodeCreateHandler = jest.fn();
const offCreate = currentDocument?.onNodeCreate(nodeCreateHandler);
const node = createNode?.({
componentName: 'TextInput',
props: {
propA: 'haha',
},
});
pageNode && node && 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();
const offDestroy = 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');
offCreate();
offDestroy();
});
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('page');
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');
formNode &&
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?.nodesMap;
const formNode = nodesMap?.get('form');
formNode &&
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?.nodesMap;
const formNode = nodesMap?.get('form') as INode;
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('场景一:插入 NodeSchemaid 与现有 schema 里的 id 重复', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode &&
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('场景一:插入 NodeSchemaid 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode &&
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?.nodesMap;
const formNode = nodesMap?.get('form');
const inputNode = currentDocument?.createNode({
componentName: 'TextInput',
id: 'nodeschema-id2',
props: {
propA: 'haha',
propB: 3,
},
});
formNode && 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?.nodesMap;
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?.nodesMap;
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?.nodesMap;
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.only('场景二:插入 Node 实例,指定 index', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as INode;
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?.nodesMap;
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);
});
});
});

View File

@ -1,56 +0,0 @@
import '../../fixtures/window';
import { Project } from '../../../src/project/project';
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 { configure: { advanced: 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);
});
});
});

View File

@ -1,456 +0,0 @@
import '../../fixtures/window';
import { Project } from '../../../src/project/project';
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 { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
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);
});
});
});

View File

@ -1,123 +0,0 @@
import { set, cloneDeep } from 'lodash-es';
import '../../fixtures/window';
import { Project } from '../../../src/project/project';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import { getIdsFromSchema } from '../../utils';
const mockCreateSettingEntry = jest.fn();
jest.mock('../../../src/designer/designer', () => {
return {
Designer: jest.fn().mockImplementation(() => {
return {
getComponentMeta() {
return {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
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);
});
});

View File

@ -1,664 +0,0 @@
// @ts-nocheck
import '../../fixtures/window';
import { set } from '../../utils';
import {
Editor,
globalContext,
Setters as InnerSetters,
} from '@alilc/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { Workspace as InnerWorkspace } from '@alilc/lowcode-workspace';
import { DocumentModel } from '../../../src/document/document-model';
import {
isRootNode,
Node,
comparePosition,
contains,
PositionNO,
} from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import divMetadata from '../../fixtures/component-metadata/div';
import dialogMetadata from '../../fixtures/component-metadata/dialog';
import btnMetadata from '../../fixtures/component-metadata/button';
import formMetadata from '../../fixtures/component-metadata/form';
import pageMetadata from '../../fixtures/component-metadata/page';
import rootHeaderMetadata from '../../fixtures/component-metadata/root-header';
import rootContentMetadata from '../../fixtures/component-metadata/root-content';
import rootFooterMetadata from '../../fixtures/component-metadata/root-footer';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
import { isNode } from '@alilc/lowcode-utils';
import { Setters } from '@alilc/lowcode-shell';
describe('Node 方法测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = new DocumentModel(project, formSchema);
editor.set('setters', new Setters(new InnerSetters()));
!globalContext.has(Editor) && globalContext.register(editor, Editor);
!globalContext.has('workspace') && globalContext.register(new InnerWorkspace(), 'workspace');
});
afterEach(() => {
project.unload();
designer.purge();
editor = null;
designer = null;
project = null;
});
// Case 1: When children is null
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const result = node.initialChildren(null);
// 预期结果是一个空数组
expect(result).toEqual([]);
});
// Case 2: When children is undefined
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const result = node.initialChildren(undefined);
// 预期结果是一个空数组
expect(result).toEqual([]);
});
// Case 3: When children is array
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const childrenArray = [{ id: 1, name: 'Child 1' }, { id: 2, name: 'Child 2' }];
const result = node.initialChildren(childrenArray);
// 预期结果是一个数组
expect(result).toEqual(childrenArray);
});
// Case 4: When children is not null and not an array
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const childObject = { id: 1, name: 'Child 1' };
const result = node.initialChildren(childObject);
// 预期结果是一个数组
expect(result).toEqual([childObject]);
});
// Case 5: When children 0
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const childObject = 0;
const result = node.initialChildren(childObject);
// 预期结果是一个数组
expect(result).toEqual([0]);
});
// Case 6: When children false
test('initialChildren returns result of initialChildren function when children is null ', () => {
const node = new Node(doc, { componentName: 'Button', props: { a: 1 } });
const childObject = false;
const result = node.initialChildren(childObject);
// 预期结果是一个数组
expect(result).toEqual([false]);
});
it('condition group', () => { });
it('getExtraProp / setExtraProp', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
expect(firstBtn.getExtraProp('non-existing', false)).toBeNull();
firstBtn.setExtraProp('xxx', '1111');
expect(firstBtn.getExtraProp('xxx', false).getValue()).toBe('1111');
});
it('import(leaf)', () => {
const form = doc.getNode('node_k1ow3cbo');
form.insert({ componentName: 'Leaf', children: '111' });
const leaf = form.getChildren().get(2);
expect(leaf.getPropValue('children')).toBe('111');
leaf.import({ componentName: 'Leaf', children: '222' });
expect(leaf.getPropValue('children')).toBe('222');
leaf.import({ componentName: 'Leaf', children: { type: 'JSExpression', value: 'state.x' } });
expect(leaf.getPropValue('children')).toEqual({ type: 'JSExpression', value: 'state.x' });
});
it('hasCondition', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.getExtraProp('condition')?.setValue(undefined);
expect(firstBtn.hasCondition()).toBeFalsy();
firstBtn.getExtraProp('condition')?.setValue(null);
expect(firstBtn.hasCondition()).toBeFalsy();
firstBtn.getExtraProp('condition')?.setValue(true);
expect(firstBtn.hasCondition()).toBeFalsy();
firstBtn.getExtraProp('condition')?.setValue('');
expect(firstBtn.hasCondition()).toBeFalsy();
firstBtn.getExtraProp('condition')?.setValue(1);
expect(firstBtn.hasCondition()).toBeTruthy();
firstBtn.getExtraProp('condition')?.setValue(false);
expect(firstBtn.hasCondition()).toBeTruthy();
});
it('hasLoop', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
expect(firstBtn.hasLoop()).toBeFalsy();
// 这里必须用 add因为 hasLoop 实现的跳过了 stash
firstBtn.props.add([1, 2], '___loop___');
expect(firstBtn.hasLoop()).toBeTruthy();
firstBtn.getExtraProp('loop')?.setValue({ type: 'JSExpression', value: 'state.a' });
expect(firstBtn.hasLoop()).toBeTruthy();
firstBtn.getExtraProp('loop')?.setValue(1);
expect(firstBtn.hasLoop()).toBeFalsy();
});
describe('getSuitablePlace', () => {
it('root子节点中有容器节点', () => {
designer.createComponentMeta(pageMetadata);
designer.createComponentMeta(rootHeaderMetadata);
designer.createComponentMeta(rootContentMetadata);
designer.createComponentMeta(rootFooterMetadata);
const rootHeaderMeta = designer.getComponentMeta('RootHeader');
set(rootHeaderMeta, 'prototype.options.canDropIn', true);
let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toEqual({
container: doc.getNode('node_k1ow3cba'),
ref: 1,
});
set(rootHeaderMeta, 'prototype.options.canDropIn', () => true);
o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toEqual({
container: doc.getNode('node_k1ow3cba'),
ref: 1,
});
});
it('root直接子节点中无容器节点自身支持放入子节点', () => {
designer.createComponentMeta(pageMetadata);
let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
const pageMeta = designer.getComponentMeta('Page');
set(pageMeta, 'prototype.options.canDropIn', () => true);
expect(o).toEqual({
container: doc.rootNode,
ref: 1,
});
set(pageMeta, 'prototype.options.canDropIn', undefined);
o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toEqual({
container: doc.rootNode,
ref: 1,
});
set(pageMeta, 'prototype.options.canDropIn', true);
o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toEqual({
container: doc.rootNode,
ref: 1,
});
});
it('root子节点中无容器节点自己也不支持放入子节点', () => {
designer.createComponentMeta(pageMetadata);
let pageMeta = designer.getComponentMeta('Page');
pageMeta = set(pageMeta, 'prototype.options.canDropIn', () => false);
let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toBeNull();
set(pageMeta, 'prototype.options.canDropIn', false);
o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
expect(o).toBeNull();
});
it('放入模态节点', () => {
designer.createComponentMeta(pageMetadata);
designer.createComponentMeta(dialogMetadata);
const dialog = doc.createNode({ componentName: 'Dialog' });
const o = doc.rootNode?.getSuitablePlace(dialog, 1);
expect(o.container).toBe(doc.rootNode);
expect(o.ref).toBe(1);
});
it('包含 focusNode', () => {
const o = doc.rootNode?.getSuitablePlace(doc.rootNode);
expect(o.container).toBe(doc.rootNode);
});
it.skip('非 root 节点,不能放入子节点', () => {
designer.createComponentMeta(formMetadata);
designer.createComponentMeta(pageMetadata);
// form 子节点以及自身都不能放入子节点
const formMeta = designer.getComponentMeta('Form');
set(formMeta, 'prototype.options.canDropIn', false);
const pageMeta = designer.getComponentMeta('Page');
set(pageMeta, 'prototype.options.canDropIn', () => true);
const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), { index: 1 });
expect(o).toEqual({
container: doc.rootNode,
ref: { index: 1 },
});
});
it('非 root 节点,能放入子节点', () => {
designer.createComponentMeta(formMetadata);
designer.createComponentMeta(pageMetadata);
// form 子节点以及自身都不能放入子节点
const formMeta = designer.getComponentMeta('Form');
set(formMeta, 'prototype.options.canDropIn', true);
const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), 1);
expect(o).toEqual({
container: doc.getNode('form'),
ref: 1,
});
});
it('null', () => {
expect(
doc.rootNode?.getSuitablePlace.call({
contains: () => false,
isContainer: () => false,
isRoot: () => false,
}),
).toBeNull();
});
});
it('removeChild / replaceWith / replaceChild', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const form = doc.getNode('node_k1ow3cbo');
// 不符合条件的节点直接返回
expect(firstBtn.replaceChild(form, { componentName: 'Button', props: { x: 1 } })).toBe(form);
firstBtn.select();
firstBtn.parent?.replaceChild(firstBtn, { componentName: 'Button', props: { x: 1 } });
expect(firstBtn.parent?.getChildren()?.size).toBe(2);
expect(firstBtn.parent?.getChildren()?.get(0)?.getPropValue('x')).toBe(1);
const secondBtn = doc.getNode('node_k1ow3cbp')!;
secondBtn.replaceWith({ componentName: 'Button', props: { y: 1 } });
expect(firstBtn.parent?.getChildren()?.size).toBe(2);
expect(firstBtn.parent?.getChildren()?.get(1)?.getPropValue('y')).toBe(1);
});
it('schema', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const schema = firstBtn.schema;
schema.props.size = 'large';
firstBtn.schema = schema;
expect(firstBtn.getPropValue('size')).toBe('large');
});
describe('插入相关方法', () => {
it('insertBefore / onChildrenChange', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const secondBtn = doc.getNode('node_k1ow3cbp')!;
const btnParent = firstBtn.parent!;
const mockFn = jest.fn();
const off = btnParent.onChildrenChange(mockFn);
// Node 实例
btnParent.insertBefore(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn);
expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ a: 1 });
expect(mockFn).toHaveBeenCalledTimes(1);
// TODO: 暂时不支持,后面补上
// // NodeSchema
// btnParent.insertBefore({ componentName: 'Button', props: { b: 1 } }, firstBtn);
// expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ b: 1 });
// expect(mockFn).toHaveBeenCalledTimes(2);
// // getComponentName
// btnParent.insertBefore({ getComponentName: () => 'Button', props: { c: 1 } }, firstBtn);
// expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ c: 1 });
// expect(mockFn).toHaveBeenCalledTimes(3);
});
it('insertAfter / onChildrenChange', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const secondBtn = doc.getNode('node_k1ow3cbp')!;
const btnParent = firstBtn.parent!;
const mockFn = jest.fn();
const off = btnParent.onChildrenChange(mockFn);
// Node 实例
btnParent.insertAfter(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn);
expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ a: 1 });
expect(mockFn).toHaveBeenCalledTimes(1);
// NodeSchema
btnParent.insertAfter({ componentName: 'Button', props: { b: 1 } }, firstBtn);
expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ b: 1 });
expect(mockFn).toHaveBeenCalledTimes(2);
// getComponentName
btnParent.insertAfter({ getComponentName: () => 'Button' }, firstBtn);
expect(btnParent.children.get(1)?.getProps().export().props).toEqual({});
expect(mockFn).toHaveBeenCalledTimes(3);
});
});
it('setVisible / getVisible / onVisibleChange', () => {
const mockFn = jest.fn();
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const off = firstBtn.onVisibleChange(mockFn);
firstBtn.setVisible(true);
expect(firstBtn.getVisible()).toBeTruthy();
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(true);
firstBtn.setVisible(false);
expect(firstBtn.getVisible()).toBeFalsy();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(false);
off();
mockFn.mockClear();
firstBtn.setVisible(true);
expect(mockFn).not.toHaveBeenCalled();
});
it('RGL / getRGL', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.isRGLContainer = true;
expect(firstBtn.isRGLContainer).toBeTruthy();
const rgl = firstBtn.getRGL();
expect(rgl.isContainerNode).toBeFalsy();
expect(rgl.isEmptyNode).toBeTruthy();
expect(rgl.isRGLContainerNode).toBeTruthy();
expect(rgl.isRGLNode).toBeFalsy();
expect(rgl.isRGL).toBeTruthy();
});
it('onPropChange', () => {
const mockFn = jest.fn();
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const off = firstBtn.onPropChange(mockFn);
firstBtn.setPropValue('x', 1);
expect(mockFn).toHaveBeenCalledTimes(1);
firstBtn.setPropValue('x', 2);
expect(mockFn).toHaveBeenCalledTimes(2);
off();
mockFn.mockClear();
firstBtn.setPropValue('x', 3);
expect(mockFn).not.toHaveBeenCalled();
});
it('addSlot / unlinkSlot / removeSlot', () => { });
it('setProps', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const secondBtn = doc.getNode('node_k1ow3cbp')!;
firstBtn.setProps(secondBtn.getProps());
expect(firstBtn.getProps()).toBe(secondBtn.getProps());
});
it('advanced initials / autoruns', async () => {
designer.createComponentMeta(pageMetadata);
const pageMeta = designer.getComponentMeta('Page');
const autorunMockFn = jest.fn();
set(pageMeta, '_transformedMetadata.configure.advanced.autoruns', [
{ name: 'a', autorun: autorunMockFn },
]);
const initialChildrenMockFn = jest.fn();
set(pageMeta, '_transformedMetadata.configure.advanced.initialChildren', initialChildrenMockFn);
doc.createNode({ componentName: 'Page', props: { a: 1 } });
expect(autorunMockFn).toHaveBeenCalled();
expect(initialChildrenMockFn).toHaveBeenCalled();
set(pageMeta, '_transformedMetadata.configure.advanced.initialChildren', {});
doc.createNode({ componentName: 'Page', props: { a: 1 } });
expect(autorunMockFn).toHaveBeenCalledTimes(2);
});
it('isValidComponent', () => {
designer.createComponentMeta(divMetadata);
expect(doc.getNode('node_k1ow3cbo')?.isValidComponent()).toBeTruthy();
expect(doc.getNode('form')?.isValidComponent()).toBeFalsy();
});
it('title', () => {
designer.createComponentMeta(btnMetadata);
const btn = doc.getNode('node_k1ow3cbn');
// 从 componentMeta 中获取到 title 值
expect(btn.title).toEqual({ type: 'i18n', 'zh-CN': '按钮', 'en-US': 'Button' });
// 从 extraProp 中获取值
btn.setExtraProp('title', 'hello button');
expect(btn.title).toBe('hello button');
// btn.props.deleteKey('___title___');
// 从 componentMeta descriptor 指向的 key 获取 title
// btn.setPropValue('xTitle', 'title from descriptor')
// expect(btn.title).toBe('title from descriptor');
});
it('isEmpty / getIndex / getIcon', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
// expect(firstBtn.children).toBeNull();
expect(firstBtn.isEmpty()).toBeTruthy();
expect(firstBtn.index).toBe(0);
expect(firstBtn.getIndex()).toBe(0);
expect(typeof firstBtn.getIcon()).toBe('function');
expect(doc.getNode('page')!.index).toBe(-1);
});
it('schema / toData / export', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
expect(firstBtn.toData().componentName).toBe('Button');
});
it('internalSetParent / internalSetWillPurge', () => {
const firstChild = doc.rootNode?.getChildren()?.get(0);
firstChild?.internalSetParent(doc.rootNode);
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.internalSetWillPurge();
// expect(firstBtn.parent).();
expect(firstBtn.hasSlots()).toBeFalsy();
});
it('prevSibling / nextSibling', () => {
// no parent
const page = doc.getNode('page');
expect(page?.nextSibling).toBeNull();
expect(page?.prevSibling).toBeNull();
// normal
const firstBtn = doc.getNode('node_k1ow3cbn');
const secondBtn = doc.getNode('node_k1ow3cbp');
expect(firstBtn?.nextSibling).toBe(secondBtn);
expect(secondBtn?.prevSibling).toBe(firstBtn);
expect(secondBtn?.nextSibling).toBeNull();
// index < 0
firstBtn?.parent?.removeChild(firstBtn);
expect(firstBtn?.nextSibling).toBeNull();
expect(firstBtn?.prevSibling).toBeNull();
});
it('toString', () => {
expect(doc.rootNode.toString()).toBe('page');
});
it('lock', () => {
const form = doc.getNode('node_k1ow3cbo');
expect(form.isLocked).toBeFalsy();
form.lock(true);
expect(form.isLocked).toBeTruthy();
form.lock(false);
expect(form.isLocked).toBeFalsy();
form.lock();
expect(form.isLocked).toBeTruthy();
});
it('didDropIn / didDropOut', () => {
const form = doc.getNode('node_k1ow3cbo');
designer.createComponentMeta(divMetadata);
designer.createComponentMeta(formMetadata);
const callbacks = form.componentMeta.advanced.callbacks;
const fn1 = callbacks.onNodeAdd = jest.fn();
const fn2 = callbacks.onNodeRemove = jest.fn();
const textField = doc.getNode('node_k1ow3cc9');
form.didDropIn(textField);
expect(fn1).toHaveBeenCalledWith(textField.internalToShellNode(), form.internalToShellNode());
form.didDropOut(textField);
expect(fn2).toHaveBeenCalledWith(textField.internalToShellNode(), form.internalToShellNode());
});
it('hover', () => {
const firstBtn = doc.getNode('node_k1ow3cbn')!;
firstBtn.hover(true);
expect(doc.designer.detecting.current).toBe(firstBtn);
firstBtn.hover(false);
expect(doc.designer.detecting.current).toBeNull();
firstBtn.hover();
expect(doc.designer.detecting.current).toBe(firstBtn);
});
it('getRect', () => {
const root = doc.rootNode!;
const firstBtn = doc.getNode('node_k1ow3cbn')!;
expect(root.getRect()).toBeNull();
expect(firstBtn.getRect()).toBeNull();
doc.project.mountSimulator({
computeRect: () => ({ x: 2, y: 2 }),
viewport: {
contentBounds: { x: 1, y: 1 },
},
});
expect(root.getRect()).toEqual({ x: 1, y: 1 });
expect(firstBtn.getRect()).toEqual({ x: 2, y: 2 });
});
it('isRootNode / isRoot / isNode', () => {
expect(isRootNode(doc.rootNode)).toBeTruthy();
expect(isNode(doc.rootNode)).toBeTruthy();
});
it('contains / comparePosition', () => {
const page = doc.getNode('page')!;
const content = doc.getNode('node_k1ow3cbb')!;
const firstBtn = doc.getNode('node_k1ow3cbn')!;
const secondBtn = doc.getNode('node_k1ow3cbp')!;
const firstCard = doc.getNode('node_k1ow3cbj')!;
expect(contains(firstBtn, firstBtn)).toBeTruthy();
expect(contains(firstBtn, secondBtn)).toBeFalsy();
expect(contains(firstBtn, page)).toBeFalsy();
expect(contains(firstBtn, content)).toBeFalsy();
expect(contains(firstCard, firstBtn)).toBeFalsy();
expect(comparePosition(firstBtn, secondBtn)).toBe(PositionNO.BeforeOrAfter);
expect(firstBtn.comparePosition(firstBtn)).toBe(PositionNO.TheSame);
expect(comparePosition(firstBtn, firstBtn)).toBe(PositionNO.TheSame);
expect(comparePosition(firstBtn, firstBtn.parent)).toBe(PositionNO.ContainedBy);
expect(comparePosition(firstBtn.parent, firstBtn)).toBe(PositionNO.Contains);
expect(comparePosition(firstCard, firstBtn)).toBe(PositionNO.BeforeOrAfter);
expect(comparePosition(firstBtn, firstCard)).toBe(PositionNO.BeforeOrAfter);
});
it('getZLevelTop', () => { });
it('propsData', () => {
expect(new Node(doc, { componentName: 'Leaf' }).propsData).toBeNull();
expect(new Node(doc, { componentName: 'Fragment' }).propsData).toBeNull();
});
describe('deprecated methods', () => {
it('setStatus / getStatus', () => {
const root = doc.rootNode!;
root.setStatus('xxx', true);
root.setStatus('locking', true);
root.setStatus('pseudo', true);
root.setStatus('inPlaceEditing', true);
expect(root.getStatus('locking')).toBeTruthy();
expect(root.getStatus('pseudo')).toBeTruthy();
expect(root.getStatus('inPlaceEditing')).toBeTruthy();
expect(root.getStatus()).toEqual({
locking: true,
pseudo: true,
inPlaceEditing: true,
});
});
it('getPage', () => {
expect(doc.rootNode?.getPage()).toBe(doc);
});
it('getDOMNode', () => {
const root = doc.rootNode!;
const firstBtn = doc.getNode('node_k1ow3cbn')!;
doc.project.mountSimulator({
findDOMNodes: () => [{ x: 1, y: 1 }],
getComponentInstances: (node) => {
if (node.componentName === 'Page') {
return [];
}
return [{}];
},
});
expect(root.getDOMNode()).toBeUndefined();
expect(firstBtn.getDOMNode()).toEqual({ x: 1, y: 1 });
});
it('registerAddon / getAddonData', () => {
const page = doc.getNode('page')!;
page.registerAddon('a', () => 'prop a');
expect(page.getAddonData('a')).toBe('prop a');
expect(page.getAddonData('b')).toBeUndefined();
expect(page.export().a).toBe('prop a');
});
it('getPrototype / setPrototype', () => {
const page = doc.getNode('page')!;
page.setPrototype({ a: 1 });
expect(page.getPrototype()).toEqual({ a: 1 });
});
});
});

View File

@ -1,37 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`valueToSource 1`] = `"1"`;
exports[`valueToSource 2`] = `"true"`;
exports[`valueToSource 3`] = `"[]"`;
exports[`valueToSource 4`] = `
"[{
\\"a\\": 1
}]"
`;
exports[`valueToSource 5`] = `
"{
\\"a\\": 1
}"
`;
exports[`valueToSource 6`] = `"null"`;
exports[`valueToSource 7`] = `"() => {}"`;
exports[`valueToSource 8`] = `"new Map()"`;
exports[`valueToSource 9`] = `"new Set()"`;
exports[`valueToSource 10`] = `"/haha/"`;
exports[`valueToSource 11`] = `"\\"hahah\\""`;
exports[`valueToSource 12`] = `"Symbol(\\"haha\\")"`;
exports[`valueToSource 13`] = `"undefined"`;
exports[`valueToSource 14`] = `"new Date(\\"2020-12-11T10:03:18.520Z\\")"`;

View File

@ -1,697 +0,0 @@
import '../../../fixtures/window';
import { Editor, engineConfig } from '@alilc/lowcode-editor-core';
import { Designer } from '../../../../src/designer/designer';
import { DocumentModel } from '../../../../src/document/document-model';
import { Prop, isProp, isValidArrayIndex } from '../../../../src/document/node/props/prop';
import { GlobalEvent, IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { shellModelFactory } from '../../../../../engine/src/modules/shell-model-factory';
const slotNodeImportMockFn = jest.fn();
const slotNodeRemoveMockFn = jest.fn();
const mockOwner = {
componentName: 'Div',
addSlot() {},
document: {
createNode(schema) {
return {
...schema,
addSlot() {},
internalSetSlotFor() {},
import: slotNodeImportMockFn,
export() {
return schema;
},
remove: slotNodeRemoveMockFn,
};
},
designer: {
editor: {
eventBus: {
emit: jest.fn(),
},
},
},
},
isInited: true,
emitPropChange: jest.fn(),
delete() {},
};
const mockPropsInst = {
owner: mockOwner,
delete() {},
};
mockPropsInst.props = mockPropsInst;
describe('Prop 类测试', () => {
describe('基础类型', () => {
let boolProp: Prop;
let strProp: Prop;
let numProp: Prop;
let nullProp: Prop;
let expProp: Prop;
let slotProp: Prop;
beforeEach(() => {
boolProp = new Prop(mockPropsInst, true, 'boolProp');
strProp = new Prop(mockPropsInst, 'haha', 'strProp');
numProp = new Prop(mockPropsInst, 1, 'numProp');
nullProp = new Prop(mockPropsInst, null, 'nullProp');
expProp = new Prop(mockPropsInst, { type: 'JSExpression', value: 'state.haha' }, 'expProp');
slotProp = new Prop(
mockPropsInst,
{
type: 'JSSlot',
title: '测试 slot',
name: 'testSlot',
params: { a: 1 },
value: [{ componentName: 'Button' }],
},
'slotProp',
);
slotNodeImportMockFn.mockClear();
slotNodeRemoveMockFn.mockClear();
});
afterEach(() => {
boolProp.purge();
strProp.purge();
numProp.purge();
nullProp.purge();
expProp.purge();
slotProp.purge();
});
it('consturctor / getProps / getNode', () => {
expect(boolProp.parent).toBe(mockPropsInst);
expect(boolProp.getProps()).toBe(mockPropsInst);
expect(boolProp.getNode()).toBe(mockOwner);
});
it('misc', () => {
expect(boolProp.get('x', false)).toBeNull();
expect(boolProp.maps).toBeNull();
expect(boolProp.add()).toBeNull();
strProp.unset();
strProp.add(2, true);
strProp.set(0);
expect(numProp.set()).toBeNull();
expect(numProp.has()).toBeFalsy();
expect(numProp.path).toEqual(['numProp']);
});
it('getValue / getAsString / setValue', () => {
expect(strProp.getValue()).toBe('haha');
strProp.setValue('heihei');
strProp.setValue('heihei');
expect(strProp.getValue()).toBe('heihei');
expect(strProp.getAsString()).toBe('heihei');
strProp.unset();
expect(strProp.getValue()).toBeUndefined();
});
it('code', () => {
expect(expProp.code).toBe('state.haha');
expect(boolProp.code).toBe('true');
expect(strProp.code).toBe('"haha"');
expProp.code = 'state.heihei';
expect(expProp.code).toBe('state.heihei');
expect(expProp.getValue()).toEqual({
type: 'JSExpression',
value: 'state.heihei',
});
boolProp.code = 'false';
expect(boolProp.code).toBe('false');
expect(boolProp.getValue()).toBe(false);
strProp.code = '"heihei"';
expect(strProp.code).toBe('"heihei"');
expect(strProp.getValue()).toBe('heihei');
// TODO: 不确定为什么会有这个分支
strProp.code = 'state.a';
expect(strProp.code).toBe('state.a');
expect(strProp.getValue()).toEqual({
type: 'JSExpression',
value: 'state.a',
mock: 'heihei',
});
});
it('export', () => {
expect(boolProp.export(IPublicEnumTransformStage.Save)).toBe(true);
expect(strProp.export(IPublicEnumTransformStage.Save)).toBe('haha');
expect(numProp.export(IPublicEnumTransformStage.Save)).toBe(1);
expect(nullProp.export(IPublicEnumTransformStage.Save)).toBe(null);
expect(nullProp.export(IPublicEnumTransformStage.Serilize)).toBe(null);
expect(expProp.export(IPublicEnumTransformStage.Save)).toEqual({
type: 'JSExpression',
value: 'state.haha',
});
strProp.unset();
expect(strProp.getValue()).toBeUndefined();
expect(strProp.isUnset()).toBeTruthy();
expect(strProp.export(IPublicEnumTransformStage.Save)).toBeUndefined();
expect(
new Prop(mockPropsInst, false, '___condition___').export(IPublicEnumTransformStage.Render),
).toBeTruthy();
engineConfig.set('enableCondition', true);
expect(
new Prop(mockPropsInst, false, '___condition___').export(IPublicEnumTransformStage.Render),
).toBeFalsy();
expect(slotProp.export(IPublicEnumTransformStage.Render)).toEqual({
type: 'JSSlot',
params: { a: 1 },
value: {
componentName: 'Slot',
title: '测试 slot',
name: 'testSlot',
params: { a: 1 },
children: [{ componentName: 'Button' }],
},
});
expect(slotProp.export(IPublicEnumTransformStage.Save)).toEqual({
type: 'JSSlot',
params: { a: 1 },
value: [{ componentName: 'Button' }],
title: '测试 slot',
name: 'testSlot',
});
});
it('compare', () => {
const newProp = new Prop(mockPropsInst, 'haha');
const newProp2 = new Prop(mockPropsInst, { a: 1 });
expect(strProp.compare(newProp)).toBe(0);
expect(strProp.compare(expProp)).toBe(2);
newProp.unset();
expect(strProp.compare(newProp)).toBe(2);
strProp.unset();
expect(strProp.compare(newProp)).toBe(0);
expect(strProp.compare(newProp2)).toBe(2);
});
it('isVirtual', () => {
expect(new Prop(mockPropsInst, 111, '!virtualProp')).toBeTruthy();
});
it('purge', () => {
boolProp.purge();
expect(boolProp.purged).toBeTruthy();
boolProp.purge();
});
it('slot', () => {
// 更新 slot
slotProp.setValue({
type: 'JSSlot',
value: [
{
componentName: 'Form',
},
],
});
expect(slotNodeImportMockFn).toBeCalled();
// 节点类型转换
slotProp.setValue(true);
expect(slotNodeRemoveMockFn).toBeCalled();
});
it('迭代器 / map / forEach', () => {
const mockFn = jest.fn();
for (const item of strProp) {
mockFn();
}
expect(mockFn).not.toHaveBeenCalled();
mockFn.mockClear();
strProp.forEach((item) => {
mockFn();
});
expect(mockFn).not.toHaveBeenCalled();
mockFn.mockClear();
strProp.map((item) => {
return mockFn();
});
expect(mockFn).not.toHaveBeenCalled();
mockFn.mockClear();
});
});
describe('复杂类型', () => {
describe('items(map 类型)', () => {
let prop: Prop;
beforeEach(() => {
prop = new Prop(mockPropsInst, {
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
emptyArr: [],
emptyObj: {},
z: {
z1: 1,
z2: 'str',
},
});
});
afterEach(() => {
prop.purge();
});
it('items / get', async () => {
expect(prop.size).toBe(7);
expect(prop.get('a').getValue()).toBe(1);
expect(prop.get('b').getValue()).toBe('str');
expect(prop.get('c').getValue()).toBe(true);
expect(prop.get('d').getValue()).toEqual({ type: 'JSExpression', value: 'state.a' });
expect(prop.get('z').getValue()).toEqual({
z1: 1,
z2: 'str',
});
expect(prop.getPropValue('a')).toBe(1);
prop.setPropValue('a', 2);
expect(prop.getPropValue('a')).toBe(2);
prop.clearPropValue('a');
expect(prop.get('a')?.isUnset()).toBeTruthy();
expect(prop.get('z.z1')?.getValue()).toBe(1);
expect(prop.get('z.z2')?.getValue()).toBe('str');
const newlyCreatedProp = prop.get('l', true);
const newlyCreatedNestedProp = prop.get('m.m1', true);
newlyCreatedProp.setValue('newlyCreatedProp');
newlyCreatedNestedProp?.setValue('newlyCreatedNestedProp');
expect(prop.get('l').getValue()).toBe('newlyCreatedProp');
expect(prop.get('m.m1').getValue()).toBe('newlyCreatedNestedProp');
const newlyCreatedNestedProp2 = prop.get('m.m2', true);
// .m2 的值为 undefined导出时将会被移除
expect(prop.get('m').getValue()).toEqual({ m1: 'newlyCreatedNestedProp' });
// 对于空值的 list / map 类型_items 应该为 null
expect(prop.get('emptyArr')._items).toBeNull();
expect(prop.get('emptyObj')._items).toBeNull();
});
it('export', () => {
expect(prop.export()).toEqual({
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
emptyArr: [],
emptyObj: {},
z: {
z1: 1,
z2: 'str',
},
});
});
it('compare', () => {
const prop1 = new Prop(mockPropsInst, { a: 1 });
const prop2 = new Prop(mockPropsInst, { b: 1 });
expect(prop1.compare(prop2)).toBe(1);
});
it('has / add / delete / deleteKey / remove', () => {
expect(prop.has('a')).toBeTruthy();
expect(prop.has('b')).toBeTruthy();
expect(prop.has('c')).toBeTruthy();
expect(prop.has('d')).toBeTruthy();
expect(prop.has('z')).toBeTruthy();
expect(prop.has('y')).toBeFalsy();
// 触发一下内部 maps 构造
prop.items;
expect(prop.has('z')).toBeTruthy();
expect(prop.add(1)).toBeNull();
prop.deleteKey('c');
expect(prop.get('c', false)).toBeNull();
prop.delete(prop.get('b'));
expect(prop.get('b', false)).toBeNull();
prop.get('d')?.remove();
expect(prop.get('d', false)).toBeNull();
});
it('set', () => {
prop.set('e', 1);
expect(prop.get('e', false)?.getValue()).toBe(1);
prop.set('a', 5);
expect(prop.get('a', false)?.getValue()).toBe(5);
});
it('迭代器 / map / forEach', () => {
const mockFn = jest.fn();
for (const item of prop) {
mockFn();
}
expect(mockFn).toHaveBeenCalledTimes(7);
mockFn.mockClear();
prop.forEach((item) => {
mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(7);
mockFn.mockClear();
prop.map((item) => {
return mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(7);
mockFn.mockClear();
});
it('dispose', () => {
prop.items;
prop.dispose();
expect(prop._items).toBeNull();
});
});
describe('items(list 类型)', () => {
let prop: Prop;
beforeEach(() => {
prop = new Prop(mockPropsInst, [1, true, 'haha']);
});
afterEach(() => {
prop.purge();
});
it('items / get', () => {
expect(prop.size).toBe(3);
expect(prop.get(0).getValue()).toBe(1);
expect(prop.get(1).getValue()).toBe(true);
expect(prop.get(2).getValue()).toBe('haha');
expect(prop.getAsString()).toBe('');
prop.unset();
prop.set(0, true);
expect(prop.set('x', 'invalid')).toBeNull();
expect(prop.get(0).getValue()).toBeTruthy();
// map / list 级联测试
prop.get('loopArgs.0', true).setValue('newItem');;
expect(prop.get('loopArgs.0').getValue()).toBe('newItem');
});
it('export', () => {
expect(prop.export()).toEqual([1, true, 'haha']);
// 触发构造
prop.items;
expect(prop.export()).toEqual([1, true, 'haha']);
});
it('compare', () => {
const prop1 = new Prop(mockPropsInst, [1]);
const prop2 = new Prop(mockPropsInst, [2]);
const prop3 = new Prop(mockPropsInst, [1, 2]);
expect(prop1.compare(prop2)).toBe(1);
expect(prop1.compare(prop3)).toBe(2);
});
it('set', () => {
prop.set(0, 1);
expect(prop.get(0, false)?.getValue()).toBe(1);
// illegal
// expect(prop.set(5, 1)).toBeNull();
});
it('should return undefined when all items are undefined', () => {
prop = new Prop(mockPropsInst, [undefined, undefined], '___loopArgs___');
expect(prop.getValue()).toEqual([undefined, undefined]);
});
it('迭代器 / map / forEach', () => {
const listProp = new Prop(mockPropsInst, [1, 2]);
const mockFn = jest.fn();
for (const item of listProp) {
mockFn();
}
expect(mockFn).toHaveBeenCalledTimes(2);
mockFn.mockClear();
listProp.forEach((item) => {
mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(2);
mockFn.mockClear();
listProp.map((item) => {
return mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(2);
mockFn.mockClear();
});
});
});
describe('slotNode / setAsSlot', () => {
const editor = new Editor();
const designer = new Designer({ editor, shellModelFactory });
const doc = new DocumentModel(designer.project, {
componentName: 'Page',
children: [
{
id: 'div',
componentName: 'Div',
},
],
});
const div = doc.getNode('div');
const slotProp = new Prop(div?.getProps(), {
type: 'JSSlot',
value: [
{
componentName: 'Button',
},
],
});
expect(slotProp.slotNode?.componentName).toBe('Slot');
// TODO: id 总是变,不好断言
expect(slotProp.code.includes('Button')).toBeTruthy();
slotProp.export();
expect(slotProp.export().value[0].componentName).toBe('Button');
expect(slotProp.export(IPublicEnumTransformStage.Serilize).value[0].componentName).toBe('Button');
slotProp.purge();
expect(slotProp.purged).toBeTruthy();
slotProp.dispose();
});
describe('slotNode-value / setAsSlot', () => {
const editor = new Editor();
const designer = new Designer({ editor, shellModelFactory });
const doc = new DocumentModel(designer.project, {
componentName: 'Page',
children: [
{
id: 'div',
componentName: 'Div',
},
],
});
const div = doc.getNode('div');
const slotProp = new Prop(div?.getProps(), {
type: 'JSSlot',
value: {
componentName: 'Slot',
id: 'node_oclei5rv2e2',
props: {
slotName: "content",
slotTitle: "主内容"
},
children: [
{
componentName: 'Button',
}
]
},
});
expect(slotProp.slotNode?.componentName).toBe('Slot');
expect(slotProp.slotNode?.title).toBe('主内容');
expect(slotProp.slotNode?.getExtraProp('name')?.getValue()).toBe('content');
expect(slotProp.slotNode?.export()?.id).toBe('node_oclei5rv2e2');
slotProp.export();
// Save
expect(slotProp.export()?.value[0].componentName).toBe('Button');
expect(slotProp.export()?.title).toBe('主内容');
expect(slotProp.export()?.name).toBe('content');
// Render
expect(slotProp.export(IPublicEnumTransformStage.Render)?.value.children[0].componentName).toBe('Button');
expect(slotProp.export(IPublicEnumTransformStage.Render)?.value.componentName).toBe('Slot');
slotProp.purge();
expect(slotProp.purged).toBeTruthy();
slotProp.dispose();
});
});
describe('其他导出函数', () => {
it('isProp', () => {
expect(isProp({ isProp: true })).toBeTruthy();
});
it('isValidArrayIndex', () => {
expect(isValidArrayIndex('1')).toBeTruthy();
expect(isValidArrayIndex('1', 2)).toBeTruthy();
expect(isValidArrayIndex('2', 1)).toBeFalsy();
});
});
describe('setValue with event', () => {
let propInstance;
let mockEmitChange;
let mockEventBusEmit;
let mockEmitPropChange;
beforeEach(() => {
// Initialize the instance of your class
propInstance = new Prop(mockPropsInst, true, 'stringProp');;
// Mock necessary methods and properties
mockEmitChange = jest.spyOn(propInstance, 'emitChange');
propInstance.owner = {
document: {
designer: {
editor: {
eventBus: {
emit: jest.fn(),
},
},
},
},
emitPropChange: jest.fn(),
delete() {},
};
mockEventBusEmit = jest.spyOn(propInstance.owner.document.designer.editor.eventBus, 'emit');
mockEmitPropChange = jest.spyOn(propInstance.owner, 'emitPropChange');
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should correctly handle string values and emit changes', () => {
const oldValue = propInstance._value;
const newValue = 'new string value';
propInstance.setValue(newValue);
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toBe(newValue);
expect(propInstance.type).toBe('literal');
expect(mockEmitChange).toHaveBeenCalledWith({ oldValue });
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
});
it('should handle object values and set type to map', () => {
const oldValue = propInstance._value;
const newValue = 234;
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue, // You can specifically test only certain keys
oldValue,
});
propInstance.setValue(newValue);
expect(propInstance.getValue()).toEqual(newValue);
expect(propInstance.type).toBe('literal');
expect(mockEmitChange).toHaveBeenCalledWith({ oldValue });
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
});
it('should has event when unset call', () => {
const oldValue = propInstance._value;
propInstance.unset();
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue: undefined, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toEqual(undefined);
expect(propInstance.type).toBe('unset');
expect(mockEmitChange).toHaveBeenCalledWith({
oldValue,
newValue: undefined,
});
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
propInstance.unset();
expect(mockEmitChange).toHaveBeenCalledTimes(1);
});
// remove
it('should has event when remove call', () => {
const oldValue = propInstance._value;
propInstance.remove();
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue: undefined, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toEqual(undefined);
// expect(propInstance.type).toBe('unset');
expect(mockEmitChange).toHaveBeenCalledWith({
oldValue,
newValue: undefined,
});
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
propInstance.remove();
expect(mockEmitChange).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,313 +0,0 @@
// @ts-nocheck
import '../../../fixtures/window';
import { set, delayObxTick } from '../../../utils';
import { Editor } from '@alilc/lowcode-editor-core';
import {
Props,
getConvertedExtraKey,
getOriginalExtraKey,
Prop,
isProp,
isValidArrayIndex,
} from '../../../../src/document/node/props/props';
import { Designer } from '../../../../src/designer/designer';
import { Project } from '../../../../src/project/project';
import { DocumentModel } from '../../../../src/document/document-model';
import { TransformStage } from '@alilc/lowcode-types';
const mockOwner = { componentName: 'Page' };
describe('Props 类测试', () => {
let props: Props;
beforeEach(() => {
props = new Props(
mockOwner,
{
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
},
{ condition: true },
);
});
afterEach(() => {
props.purge();
});
it('getNode', () => {
expect(props.getNode()).toBe(mockOwner);
});
it('items / get', async () => {
expect(props.size).toBe(6);
expect(props.get('a').getValue()).toBe(1);
expect(props.get('b').getValue()).toBe('str');
expect(props.get('c').getValue()).toBe(true);
expect(props.get('d').getValue()).toEqual({ type: 'JSExpression', value: 'state.a' });
expect(props.get('z').getValue()).toEqual({
z1: 1,
z2: 'str',
});
expect(props.getPropValue('a')).toBe(1);
props.setPropValue('a', 2);
expect(props.getPropValue('a')).toBe(2);
// props.clearPropValue('a');
// expect(props.get('a')?.isUnset()).toBeTruthy();
expect(props.get('z.z1')?.getValue()).toBe(1);
expect(props.get('z.z2')?.getValue()).toBe('str');
const notCreatedProp = props.get('i');
expect(notCreatedProp).toBeNull();
const newlyCreatedProp = props.get('l', true);
const newlyCreatedNestedProp = props.get('m.m1', true);
newlyCreatedProp.setValue('newlyCreatedProp');
newlyCreatedNestedProp?.setValue('newlyCreatedNestedProp');
expect(props.get('l').getValue()).toBe('newlyCreatedProp');
expect(props.get('m.m1').getValue()).toBe('newlyCreatedNestedProp');
// map / list 级联测试
props.get('loopArgs.0', true).setValue('newItem');
expect(props.get('loopArgs.0').getValue()).toBe('newItem');
});
it('export', () => {
expect(props.export()).toEqual({
props: {
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
},
extras: {
condition: true,
},
});
expect(props.toData()).toEqual({
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
});
props.get('a')?.unset();
expect(props.toData()).toEqual({
a: undefined,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
});
});
it('export - remove undefined items', () => {
props.import(
{
a: 1,
},
{ loop: false },
);
props.setPropValue('x', undefined);
expect(props.export()).toEqual({
props: {
a: 1,
},
extras: {
loop: false,
},
});
props.setPropValue('x', 2);
expect(props.export()).toEqual({
props: {
a: 1,
x: 2,
},
extras: {
loop: false,
},
});
props.setPropValue('y.z', undefined);
expect(props.export()).toEqual({
props: {
a: 1,
x: 2,
},
extras: {
loop: false,
},
});
props.setPropValue('y.z', 2);
expect(props.export()).toEqual({
props: {
a: 1,
x: 2,
y: { z: 2 },
},
extras: {
loop: false,
},
});
});
it('import', () => {
props.import(
{
x: 1,
y: true,
},
{ loop: false },
);
expect(props.export()).toEqual({
props: {
x: 1,
y: true,
},
extras: {
loop: false,
},
});
props.import();
});
it('merge', async () => {
props.merge({ x: 1 });
await delayObxTick();
expect(props.get('x')?.getValue()).toBe(1);
});
it('has / add / delete / deleteKey / remove', () => {
expect(props.has('a')).toBeTruthy();
expect(props.has('b')).toBeTruthy();
expect(props.has('c')).toBeTruthy();
expect(props.has('d')).toBeTruthy();
expect(props.has('z')).toBeTruthy();
expect(props.has('y')).toBeFalsy();
props.add(1, 'newAdded');
expect(props.has('newAdded')).toBeTruthy();
props.deleteKey('c');
expect(props.get('c', false)).toBeNull();
props.delete(props.get('b'));
expect(props.get('b', false)).toBeNull();
props.get('d')?.remove();
expect(props.get('d', false)).toBeNull();
});
it('迭代器 / map / forEach', () => {
const mockFn = jest.fn();
for (const item of props) {
mockFn();
}
expect(mockFn).toHaveBeenCalledTimes(6);
mockFn.mockClear();
props.forEach((item) => {
mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(6);
mockFn.mockClear();
props.map((item) => {
return mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(6);
mockFn.mockClear();
props.filter((item) => {
return mockFn();
});
expect(mockFn).toHaveBeenCalledTimes(6);
mockFn.mockClear();
});
it('purge', () => {
props.purge();
expect(props.purged).toBeTruthy();
});
it('empty items', () => {
expect(new Props(mockOwner).export()).toEqual({});
});
describe('list 类型', () => {
let props: Props;
beforeEach(() => {
props = new Props(mockOwner, [1, true, 'haha'], { condition: true });
});
it('constructor', () => {
props.purge();
});
it('export', () => {
expect(props.export().extras).toEqual({
condition: true,
});
});
it('import', () => {
props.import([1], { loop: true });
expect(props.export().extras).toEqual({
loop: true,
});
props.items[0]?.unset();
props.export();
});
});
});
describe('其他函数', () => {
it('getConvertedExtraKey', () => {
expect(getConvertedExtraKey()).toBe('');
expect(getConvertedExtraKey('a')).toBe('___a___');
expect(getConvertedExtraKey('a.b')).toBe('___a___.b');
expect(getConvertedExtraKey('a.0')).toBe('___a___.0');
});
it('getOriginalExtraKey', () => {
expect(getOriginalExtraKey('___a___')).toBe('a');
expect(getOriginalExtraKey('___a___.b')).toBe('a.b');
});
});

View File

@ -1,29 +0,0 @@
// @ts-nocheck
import '../../../fixtures/silent-console';
import { getSource, valueToSource } from '../../../../src/document/node/props/value-to-source';
it('valueToSource', () => {
expect(valueToSource(1)).toMatchSnapshot();
expect(valueToSource(true)).toMatchSnapshot();
expect(valueToSource([])).toMatchSnapshot();
expect(valueToSource([{ a: 1 }])).toMatchSnapshot();
expect(valueToSource({ a: 1 })).toMatchSnapshot();
expect(valueToSource(null)).toMatchSnapshot();
expect(valueToSource(() => {})).toMatchSnapshot();
expect(valueToSource(new Map())).toMatchSnapshot();
expect(valueToSource(new Set())).toMatchSnapshot();
expect(valueToSource(/haha/)).toMatchSnapshot();
expect(valueToSource('hahah')).toMatchSnapshot();
expect(valueToSource(Symbol('haha'))).toMatchSnapshot();
expect(valueToSource()).toMatchSnapshot();
expect(valueToSource(new Date(1607680998520))).toMatchSnapshot();
});
it('getSource', () => {
expect(getSource({ __source: { a: 1 } })).toEqual({ a: 1 });
expect(getSource()).toBe('');
const value = { abc: 1 };
getSource(value);
expect(value).toHaveProperty('__source');
expect(getSource(1)).toBe('1');
});

View File

@ -1,263 +0,0 @@
import '../fixtures/window';
import { Project } from '../../src/project/project';
import { Designer } from '../../src/designer/designer';
import formSchema from '../fixtures/schema/form';
const mockCreateSettingEntry = jest.fn();
jest.mock('../../src/designer/designer', () => {
return {
Designer: jest.fn().mockImplementation(() => {
return {
getComponentMeta() {
return {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
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_fake');
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('selectAll 包含不存在的 id', () => {
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']);
expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
});
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']);
const selectionChangeHandler = jest.fn();
selection.onSelectionChange(selectionChangeHandler);
selection.dispose();
expect(selectionChangeHandler).not.toHaveBeenCalled();
});
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('page'))).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('page');
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['page']);
expect(selection.selected).toEqual(['page']);
expect(selection.has('page')).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('page');
expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
expect(selection.selected).toEqual(['page']);
selectionChangeHandler.mockClear();
});
it('getNodes', () => {
const project = new Project(designer, {
componentsTree: [formSchema],
});
project.open();
const { currentDocument } = project;
const { selection } = currentDocument!;
selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
// form2 is not a valid node
expect(selection.getNodes()).toHaveLength(2);
});
it('getTopNodes - BeforeOrAfter', () => {
const project = new Project(designer, {
componentsTree: [formSchema],
});
project.open();
const { currentDocument } = project;
const { selection } = currentDocument!;
selection.selectAll(['node_k1ow3cbj', 'node_k1ow3cbo']);
expect(selection.getTopNodes()).toHaveLength(2);
});
it('getTopNodes', () => {
const project = new Project(designer, {
componentsTree: [formSchema],
});
project.open();
const { currentDocument } = project;
const { selection } = currentDocument!;
selection.selectAll(['node_k1ow3cbj', 'node_k1ow3cbo', 'form', 'node_k1ow3cbl', 'form2']);
// form2 is not a valid node, and node_k1ow3cbj is a child node of form
expect(selection.getTopNodes()).toHaveLength(1);
});
});

View File

@ -1,280 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Abc.Group',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: { label: '容器' },
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
},
supports: {},
advanced: {
isTopFixed: true,
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,280 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Abc.Item',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: { label: '容器' },
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
},
supports: {},
advanced: {
isTopFixed: true,
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,280 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Abc.Node',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: { label: '容器' },
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
},
supports: {},
advanced: {
isTopFixed: true,
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,280 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Abc.Option',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: { label: '容器' },
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
},
supports: {},
advanced: {
isTopFixed: true,
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,308 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Button',
npm: {
package: '@ali/vc-button',
componentName: 'Button',
},
title: '按钮',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: 'field',
name: 'non-exsiting',
},
{
type: 'field',
name: 'obj',
items: [
{
name: 'a',
title: 'a',
setter: () => 'StringSetter',
},
{
name: 'b',
title: 'b',
setter: 'NumberSetter',
},
{
name: 'c',
title: 'c',
setter: {
componentName: 'ColorSetter'
},
},
],
},
() => 'haha', // IPublicTypeCustomView
{
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: {
nestingRule: {
// parentWhitelist: 'Div',
// childWhitelist: 'Div',
},
descriptor: 'xTitle'
},
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,277 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Dialog',
npm: {
package: '@ali/vc-dialog',
componentName: 'Dialog',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
isModal: true,
},
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,283 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: {},
advanced: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,22 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
devMode: 'proCode',
tags: ['布局'],
configure: {
component: {
nestingRule: {
parentWhitelist: (parent, my) => {
if (parent.componentName === 'Form' && my.componentName === 'Div') return true;
return false;
},
childWhitelist: (child, my) => {
if (child.componentName === 'Image' && my.componentName === 'Div') return true;
return false;
},
},
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,280 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: { label: '容器' },
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,
},
supports: {},
advanced: {
isTopFixed: true,
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,282 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: {},
advanced: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,272 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
devMode: 'proCode',
tags: ['布局'],
configure: [
{
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',
},
],
},
],
experimental: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,283 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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',
},
disableBehaviors: '*',
},
supports: {},
advanced: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,283 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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',
},
disableBehaviors: '*',
},
supports: {},
},
experimental: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,276 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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',
},
],
},
],
supports: {},
advanced: {
callbacks: {
onNodeAdd: (dragment, self) => { console.log(dragment); },
onNodeRemove: (dragment, self) => { console.log(dragment); }
},
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: [],
},
},
} as IPublicTypeComponentMetadata;

View File

@ -1,12 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
npm: {
package: '@ali/vc-div',
componentName: 'Div',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
devMode: 'proCode',
tags: ['布局'],
} as IPublicTypeComponentMetadata;

View File

@ -1,8 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Div',
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
devMode: 'proCode',
tags: ['布局'],
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Form',
npm: {
package: '@ali/vc-form',
},
title: '表单',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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,Page',
// 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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Other',
npm: {
package: '@ali/vc-other',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Page',
npm: {
package: '@ali/vc-page',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'Page',
npm: {
package: '@ali/vc-page',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'RootContent',
npm: {
package: '@ali/vc-page',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'RootFooter',
npm: {
package: '@ali/vc-page',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,279 +0,0 @@
import { IPublicTypeComponentMetadata } from "@alilc/lowcode-types";
export default {
componentName: 'RootHeader',
npm: {
package: '@ali/vc-page',
},
title: '容器',
docUrl: 'https://github.com/alibaba/lowcode-materials/tree/main/docs',
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: [],
},
} as IPublicTypeComponentMetadata;

View File

@ -1,3 +0,0 @@
Object.defineProperty(window, 'requestAnimationFrame', {
value: null,
});

File diff suppressed because it is too large Load Diff

View File

@ -1,993 +0,0 @@
export default {
componentName: 'Page',
id: 'page',
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': '',
'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': '',
'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': '',
'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': '',
'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': '',
'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': '',
'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': '',
'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': '',
'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,
},
],
i18n: {
'zh-CN': {
'i18n-jwg27yo4': '你好',
'i18n-jwg27yo3': '中国',
},
'en-US': {
'i18n-jwg27yo4': 'Hello',
'i18n-jwg27yo3': 'China',
},
},
};

View File

@ -1,90 +0,0 @@
export default {
componentName: 'Page',
id: 'page',
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',
},
],
};

View File

@ -1,6 +0,0 @@
export const mockConsoleError = jest.fn();
export const mockConsoleWarn = jest.fn();
// const mockConsoleInfo = jest.fn();
console.error = mockConsoleError;
console.warn = mockConsoleWarn;

View File

@ -1,7 +0,0 @@
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;
}

View File

@ -1,28 +0,0 @@
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: {},
});
window.scrollTo = () => {};
window.console.warn = () => {};
const originalLog = window.console.log;
window.console.log = (...args) => {
// suppress boring warnings
if (args[0]?.includes && args[0].includes('@babel/plugin-proposal-private-property-in-object')) return;
originalLog.apply(window.console, args);
};
window.React = window.React || {};

View File

@ -1,252 +0,0 @@
import '../../fixtures/window';
import { Designer } from '../../../src/designer/designer';
import divMeta from '../../fixtures/component-metadata/div';
import div2Meta from '../../fixtures/component-metadata/div2';
import div3Meta from '../../fixtures/component-metadata/div3';
import div4Meta from '../../fixtures/component-metadata/div4';
import div5Meta from '../../fixtures/component-metadata/div5';
import div6Meta from '../../fixtures/component-metadata/div6';
import div7Meta from '../../fixtures/component-metadata/div7';
import div8Meta from '../../fixtures/component-metadata/div8';
import div9Meta from '../../fixtures/component-metadata/div9';
import div10Meta from '../../fixtures/component-metadata/div10';
import abcgroup from '../../fixtures/component-metadata/abcgroup';
import abcitem from '../../fixtures/component-metadata/abcitem';
import abcnode from '../../fixtures/component-metadata/abcnode';
import abcoption from '../../fixtures/component-metadata/abcoption';
import page2Meta from '../../fixtures/component-metadata/page2';
import {
ComponentMeta,
isComponentMeta,
ensureAList,
buildFilter,
} from '../../../src/component-meta';
jest.mock('../../../src/designer/designer', () => {
return {
Designer: jest.fn().mockImplementation(() => {
const { ComponentActions } = require('../../../src/component-actions');
return {
getGlobalComponentActions: () => [],
componentActions: new ComponentActions(),
};
}),
};
});
let designer = null;
beforeAll(() => {
designer = new Designer({} as any);
});
describe('组件元数据处理', () => {
it('构造函数', () => {
const meta = new ComponentMeta(designer, divMeta);
expect(meta.isContainer).toBeTruthy();
expect(isComponentMeta(meta)).toBeTruthy();
expect(meta.acceptable).toBeFalsy();
expect(meta.isRootComponent()).toBeFalsy();
expect(meta.isModal).toBeFalsy();
expect(meta.rootSelector).toBeUndefined();
expect(meta.liveTextEditing).toBeUndefined();
expect(meta.descriptor).toBeUndefined();
expect(typeof meta.icon).toBe('function');
expect(meta.getMetadata().title).toBe('容器');
expect(meta.title).toEqual({ type: 'i18n', 'en-US': 'Div', 'zh-CN': '容器' });
expect(meta.isMinimalRenderUnit).toBeFalsy();
expect(meta.isTopFixed).toBeFalsy();
meta.setNpm({ package: '@ali/vc-div', componentName: 'Div' });
expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' });
meta.npm = { package: '@ali/vc-div', componentName: 'Div' };
expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' });
const mockFn = jest.fn();
const offFn = meta.onMetadataChange(mockFn);
meta.setMetadata(divMeta);
expect(mockFn).toHaveBeenCalledTimes(1);
offFn();
meta.setMetadata(divMeta);
// 不会再触发函数
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('构造函数 - 兼容场景title 是个普通对象)', () => {
const meta = new ComponentMeta(designer, div2Meta);
expect(meta.title).toEqual('容器');
expect(meta.isTopFixed).toBeTruthy();
});
it('构造函数 - 兼容场景title fallback 到 componentName', () => {
const meta = new ComponentMeta(designer, div3Meta);
expect(meta.title).toEqual('Div');
});
it('构造函数 - 兼容场景configure 是个数组)', () => {
const meta = new ComponentMeta(designer, div4Meta);
expect(meta.configure).toEqual(div4Meta.configure);
});
it('构造函数 - 兼容场景(使用 experimental', () => {
const meta = new ComponentMeta(designer, div6Meta);
expect(meta.getMetadata().configure.advanced.initials).toHaveLength(9);
});
it('构造函数 - 兼容场景(没有 configure.component', () => {
const meta = new ComponentMeta(designer, div7Meta);
expect(meta.isContainer).toBeFalsy();
expect(meta.isModal).toBeFalsy();
});
it('构造函数 - 兼容场景(没有 configure', () => {
const meta = new ComponentMeta(designer, div8Meta);
expect(meta.configure).toEqual([]);
});
it('构造函数 - 兼容场景(没有 npm', () => {
const meta = new ComponentMeta(designer, div9Meta);
expect(meta.npm).toBeUndefined();
meta.setNpm({ package: '@ali/vc-div', componentName: 'Div' });
expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' });
});
it('availableActions', () => {
const meta = new ComponentMeta(designer, divMeta);
expect(meta.availableActions).toHaveLength(5);
expect(meta.availableActions[0].name).toBe('remove');
expect(meta.availableActions[1].name).toBe('hide');
expect(meta.availableActions[2].name).toBe('copy');
designer.componentActions.removeBuiltinComponentAction('remove');
expect(meta.availableActions).toHaveLength(4);
expect(meta.availableActions[0].name).toBe('hide');
expect(meta.availableActions[1].name).toBe('copy');
designer.componentActions.addBuiltinComponentAction({
name: 'new',
content: {
action() {},
},
});
expect(meta.availableActions).toHaveLength(5);
expect(meta.availableActions[0].name).toBe('hide');
expect(meta.availableActions[1].name).toBe('copy');
expect(meta.availableActions[4].name).toBe('new');
});
it('availableActions - disableBehaviors: *', () => {
const meta = new ComponentMeta(designer, div5Meta);
expect(meta.availableActions).toHaveLength(0);
});
it('availableActions - rootCompoment', () => {
const meta = new ComponentMeta(designer, page2Meta);
// (hide + new) left
expect(meta.availableActions).toHaveLength(2);
});
describe('checkNesting', () => {
const mockNode = (componentName) => {
return {
internalToShellNode() {
return {
componentName,
};
},
isNode: true,
};
};
const mockNodeForm = mockNode('Form');
const mockNodeImage = mockNode('Image');
const mockNodeDiv = mockNode('Div');
it('checkNestingUp', () => {
const meta1 = new ComponentMeta(designer, divMeta);
// 没有配置 parentWhitelist判断默认为 true
expect(meta1.checkNestingUp(mockNodeDiv, mockNodeDiv)).toBeTruthy();
const meta2 = new ComponentMeta(designer, div10Meta);
expect(meta2.checkNestingUp(mockNodeDiv, mockNodeForm)).toBeTruthy();
expect(meta2.checkNestingUp(mockNodeDiv, mockNodeDiv)).toBeFalsy();
});
it('checkNestingDown', () => {
const meta1 = new ComponentMeta(designer, divMeta);
// 没有配置 childWhitelist判断默认为 true
expect(meta1.checkNestingDown(mockNodeDiv, mockNodeDiv)).toBeTruthy();
const meta2 = new ComponentMeta(designer, div10Meta);
expect(meta2.checkNestingDown(mockNodeDiv, mockNodeForm)).toBeFalsy();
expect(meta2.checkNestingDown(mockNodeDiv, mockNodeImage)).toBeTruthy();
});
});
});
describe('组件元数据 transducers', () => {
it('legacyIssues', () => {
const legacyMeta: any = {
...divMeta,
devMode: 'procode',
};
const meta = new ComponentMeta(designer, legacyMeta);
const metadata = meta.getMetadata();
expect(metadata.devMode).toBe('proCode');
});
});
describe('帮助函数', () => {
it('ensureAList', () => {
expect(ensureAList()).toBeNull();
expect(ensureAList(1)).toBeNull();
expect(ensureAList([])).toBeNull();
expect(ensureAList('copy lock')).toEqual(['copy', 'lock']);
expect(ensureAList(['copy', 'lock'])).toEqual(['copy', 'lock']);
});
it('buildFilter', () => {
const mockFn = () => {};
expect(buildFilter()).toBeNull();
expect(buildFilter([])).toBeNull();
expect(buildFilter(mockFn)).toBe(mockFn);
const mockRE = /xxx/;
const filter = buildFilter(mockRE);
expect(filter({ componentName: 'xxx' })).toBeTruthy();
expect(filter({ componentName: 'yyy' })).toBeFalsy();
expect(buildFilter('xxx yyy')({ componentName: 'xxx' })).toBeTruthy();
expect(buildFilter('xxx yyy')({ componentName: 'zzz' })).toBeFalsy();
});
it('registerMetadataTransducer', () => {
expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(2);
// 插入到 legacy-issues 和 component-defaults 的中间
designer.componentActions.registerMetadataTransducer((metadata) => metadata, 3, 'noop');
expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(3);
designer.componentActions.registerMetadataTransducer((metadata) => metadata);
expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(4);
});
it('modifyBuiltinComponentAction', () => {
designer.componentActions.modifyBuiltinComponentAction('copy', (action) => {
expect(action.name).toBe('copy');
});
});
});
describe('transducers', () => {
it('componentDefaults', () => {
const meta1 = new ComponentMeta(designer, abcgroup);
const meta2 = new ComponentMeta(designer, abcitem);
const meta3 = new ComponentMeta(designer, abcnode);
const meta4 = new ComponentMeta(designer, abcoption);
expect(meta1.getMetadata().configure.component.nestingRule.childWhitelist).toEqual(['Abc']);
expect(meta2.getMetadata().configure.component.nestingRule.parentWhitelist).toEqual(['Abc']);
expect(meta3.getMetadata().configure.component.nestingRule.parentWhitelist).toEqual(['Abc', 'Abc.Node']);
expect(meta4.getMetadata().configure.component.nestingRule.parentWhitelist).toEqual(['Abc']);
});
});

View File

@ -1,7 +0,0 @@
import '../fixtures/window';
import { isSimulatorHost } from '../../src/simulator';
it('isSimulatorHost', () => {
expect(isSimulatorHost({ isSimulator: true })).toBeTruthy();
expect(isSimulatorHost({ a: 1 })).toBeFalsy();
});

View File

@ -1,529 +0,0 @@
import '../fixtures/window';
import { Editor, engineConfig } from '@alilc/lowcode-editor-core';
import { LowCodePluginManager } from '../../src/plugin/plugin-manager';
import { IPublicModelPluginContext, IPublicApiPlugins } from '@alilc/lowcode-types';
import { ILowCodePluginContextPrivate } from '../../src/plugin/plugin-types';
const editor = new Editor();
let contextApiAssembler;
describe('plugin 测试', () => {
let pluginManager: IPublicApiPlugins;
beforeEach(() => {
contextApiAssembler = {
assembleApis(context: ILowCodePluginContextPrivate){
context.plugins = pluginManager as IPublicApiPlugins;
// mock set apis
}
};
pluginManager = new LowCodePluginManager(contextApiAssembler).toProxy();
});
afterEach(() => {
pluginManager.dispose();
});
it('注册插件,插件参数生成函数能被调用,且能拿到正确的 ctx ', () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
mockFn(ctx);
return {
init: jest.fn(),
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
const [expectedCtx] = mockFn.mock.calls[0];
expect(expectedCtx).toHaveProperty('project');
expect(expectedCtx).toHaveProperty('setters');
expect(expectedCtx).toHaveProperty('material');
expect(expectedCtx).toHaveProperty('hotkey');
expect(expectedCtx).toHaveProperty('plugins');
expect(expectedCtx).toHaveProperty('skeleton');
expect(expectedCtx).toHaveProperty('logger');
expect(expectedCtx).toHaveProperty('config');
expect(expectedCtx).toHaveProperty('event');
expect(expectedCtx).toHaveProperty('preference');
});
it('注册插件,调用插件 init 方法', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
exports() {
return {
x: 1,
y: 2,
};
},
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(pluginManager.size).toBe(1);
expect(pluginManager.has('demo1')).toBeTruthy();
expect(pluginManager.get('demo1')!.isInited()).toBeTruthy();
expect(pluginManager.demo1).toBeTruthy();
expect(pluginManager.demo1.x).toBe(1);
expect(pluginManager.demo1.y).toBe(2);
expect(pluginManager.demo1.z).toBeUndefined();
expect(mockFn).toHaveBeenCalled();
});
it('注册插件,调用 setDisabled 方法', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(pluginManager.demo1).toBeTruthy();
pluginManager.setDisabled('demo1', true);
expect(pluginManager.demo1).toBeUndefined();
});
it('注册插件,调用 plugin.setDisabled 方法', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(pluginManager.demo1).toBeTruthy();
pluginManager.get('demo1').setDisabled();
expect(pluginManager.demo1).toBeUndefined();
});
it('删除插件,调用插件 destroy 方法', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
init: jest.fn(),
destroy: mockFn,
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
await pluginManager.delete('demo1');
expect(mockFn).toHaveBeenCalled();
await pluginManager.delete('non-existing');
});
describe('dependencies 依赖', () => {
it('dependencies 依赖', async () => {
const mockFn = jest.fn();
const creator21 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo1'),
};
};
creator21.pluginName = 'demo1';
creator21.meta = {
dependencies: ['demo2'],
};
pluginManager.register(creator21);
const creator22 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo2'),
};
};
creator22.pluginName = 'demo2';
pluginManager.register(creator22);
await pluginManager.init();
expect(mockFn).toHaveBeenNthCalledWith(1, 'demo2');
expect(mockFn).toHaveBeenNthCalledWith(2, 'demo1');
});
it('dependencies 依赖 - string', async () => {
const mockFn = jest.fn();
const creator21 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo1'),
};
};
creator21.pluginName = 'demo1';
creator21.meta = {
dependencies: 'demo2',
};
pluginManager.register(creator21);
const creator22 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo2'),
};
};
creator22.pluginName = 'demo2';
pluginManager.register(creator22);
await pluginManager.init();
expect(mockFn).toHaveBeenNthCalledWith(1, 'demo2');
expect(mockFn).toHaveBeenNthCalledWith(2, 'demo1');
});
it('dependencies 依赖 - 兼容 dep', async () => {
const mockFn = jest.fn();
const creator21 = (ctx: IPublicModelPluginContext) => {
return {
dep: ['demo4'],
init: () => mockFn('demo3'),
};
};
creator21.pluginName = 'demo3';
pluginManager.register(creator21);
const creator22 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo4'),
};
};
creator22.pluginName = 'demo4';
pluginManager.register(creator22);
await pluginManager.init();
expect(mockFn).toHaveBeenNthCalledWith(1, 'demo4');
expect(mockFn).toHaveBeenNthCalledWith(2, 'demo3');
});
it('dependencies 依赖 - 兼容 dep & string', async () => {
const mockFn = jest.fn();
const creator21 = (ctx: IPublicModelPluginContext) => {
return {
dep: 'demo4',
init: () => mockFn('demo3'),
};
};
creator21.pluginName = 'demo3';
pluginManager.register(creator21);
const creator22 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo4'),
};
};
creator22.pluginName = 'demo4';
pluginManager.register(creator22);
await pluginManager.init();
expect(mockFn).toHaveBeenNthCalledWith(1, 'demo4');
expect(mockFn).toHaveBeenNthCalledWith(2, 'demo3');
});
});
it('version 依赖', async () => {
const mockFn = jest.fn();
const creator21 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo1'),
};
};
creator21.pluginName = 'demo1';
creator21.meta = {
engines: {
lowcodeEngine: '^1.1.0',
},
};
engineConfig.set('ENGINE_VERSION', '1.0.1');
console.log('version: ', engineConfig.get('ENGINE_VERSION'));
// not match should skip
pluginManager.register(creator21).catch((e) => {
expect(e).toEqual(
new Error(
'plugin demo1 skipped, engine check failed, current engine version is 1.0.1, meta.engines.lowcodeEngine is ^1.1.0',
),
);
});
expect(pluginManager.plugins.length).toBe(0);
const creator22 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo2'),
};
};
creator22.pluginName = 'demo2';
creator22.meta = {
engines: {
lowcodeEngine: '^1.0.1',
},
};
engineConfig.set('ENGINE_VERSION', '1.0.3');
pluginManager.register(creator22);
expect(pluginManager.plugins.length).toBe(1);
const creator23 = (ctx: IPublicModelPluginContext) => {
return {
init: () => mockFn('demo3'),
};
};
creator23.pluginName = 'demo3';
creator23.meta = {
engines: {
lowcodeEngine: '1.x',
},
};
engineConfig.set('ENGINE_VERSION', '1.1.1');
pluginManager.register(creator23);
expect(pluginManager.plugins.length).toBe(2);
});
it('autoInit 功能', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
};
};
creator2.pluginName = 'demo1';
await pluginManager.register(creator2, { autoInit: true });
expect(mockFn).toHaveBeenCalled();
});
it('插件不会重复 init除非强制重新 init', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
name: 'demo1',
init: mockFn,
};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(mockFn).toHaveBeenCalledTimes(1);
pluginManager.get('demo1')!.init();
expect(mockFn).toHaveBeenCalledTimes(1);
pluginManager.get('demo1')!.init(true);
expect(mockFn).toHaveBeenCalledTimes(2);
});
it('默认情况不允许重复注册', async () => {
const mockFn = jest.fn();
const mockPlugin = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
};
};
mockPlugin.pluginName = 'demoDuplicated';
pluginManager.register(mockPlugin);
pluginManager.register(mockPlugin).catch((e) => {
expect(e).toEqual(new Error('Plugin with name demoDuplicated exists'));
});
await pluginManager.init();
});
it('插件增加 override 参数时可以重复注册', async () => {
const mockFn = jest.fn();
const mockPlugin = (ctx: IPublicModelPluginContext) => {
return {
init: mockFn,
};
};
mockPlugin.pluginName = 'demoOverride';
pluginManager.register(mockPlugin);
pluginManager.register(mockPlugin, { override: true });
await pluginManager.init();
});
it('插件增加 override 参数时可以重复注册, 被覆盖的如果已初始化,会被销毁', async () => {
const mockInitFn = jest.fn();
const mockDestroyFn = jest.fn();
const mockPlugin = (ctx: IPublicModelPluginContext) => {
return {
init: mockInitFn,
destroy: mockDestroyFn,
};
};
mockPlugin.pluginName = 'demoOverride';
await pluginManager.register(mockPlugin, { autoInit: true });
expect(mockInitFn).toHaveBeenCalledTimes(1);
await pluginManager.register(mockPlugin, { override: true });
expect(mockDestroyFn).toHaveBeenCalledTimes(1);
await pluginManager.init();
});
it('dispose 方法', async () => {
const creator2 = (ctx: IPublicModelPluginContext) => {
return {};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
const plugin = pluginManager.get('demo1')!;
await plugin.dispose();
expect(pluginManager.has('demo1')).toBeFalsy();
});
it('getAll 方法', async () => {
const creator2 = (ctx: IPublicModelPluginContext) => {
return {};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(pluginManager.getAll()).toHaveLength(1);
});
it('getPluginPreference 方法 - null', async () => {
const creator2 = (ctx: IPublicModelPluginContext) => {
return {};
};
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init();
expect(pluginManager.getPluginPreference()).toBeNull();
});
it('getPluginPreference 方法', async () => {
const creator2 = (ctx: IPublicModelPluginContext) => {
return {};
};
const preference = new Map();
preference.set('demo1', { a: 1, b: 2 });
creator2.pluginName = 'demo1';
pluginManager.register(creator2);
await pluginManager.init(preference);
expect(pluginManager.getPluginPreference('demo1')).toEqual({ a: 1, b: 2 });
});
it('注册插件,调用插件 init 方法并传入 preference可以成功获取', async () => {
const mockFn = jest.fn();
const mockFnForCtx = jest.fn();
const mockFnForCtx2 = jest.fn();
const mockPreference = new Map();
mockPreference.set('demo1', {
key1: 'value for key1',
key2: false,
key3: 123,
key5: 'value for key5, but declared, should not work',
});
const creator2 = (ctx: IPublicModelPluginContext) => {
mockFnForCtx(ctx);
return {
init: jest.fn(),
};
};
creator2.pluginName = 'demo1';
creator2.meta = {
preferenceDeclaration: {
title: 'demo1的的参数定义',
properties: [
{
key: 'key1',
type: 'string',
description: 'this is description for key1',
},
{
key: 'key2',
type: 'boolean',
description: 'this is description for key2',
},
{
key: 'key3',
type: 'number',
description: 'this is description for key3',
},
{
key: 'key4',
type: 'string',
description: 'this is description for key4',
},
],
},
};
const creator22 = (ctx: IPublicModelPluginContext) => {
mockFnForCtx2(ctx);
return {
init: jest.fn(),
};
};
creator22.pluginName = 'demo2';
creator22.meta = {
preferenceDeclaration: {
title: 'demo1的的参数定义',
properties: [
{
key: 'key1',
type: 'string',
description: 'this is description for key1',
},
],
},
};
pluginManager.register(creator2);
pluginManager.register(creator22);
expect(mockFnForCtx).toHaveBeenCalledTimes(1);
await pluginManager.init(mockPreference);
// creator2 only get excuted once
expect(mockFnForCtx).toHaveBeenCalledTimes(1);
const [expectedCtx, expectedOptions] = mockFnForCtx.mock.calls[0];
expect(expectedCtx).toHaveProperty('preference');
// test normal case
expect(expectedCtx.preference.getPreferenceValue('key1', 'default')).toBe('value for key1');
// test default value logic
expect(expectedCtx.preference.getPreferenceValue('key4', 'default for key4')).toBe(
'default for key4',
);
// test undeclared key
expect(expectedCtx.preference.getPreferenceValue('key5', 'default for key5')).toBeUndefined();
// no preference defined
const [expectedCtx2] = mockFnForCtx2.mock.calls[0];
expect(expectedCtx2.preference.getPreferenceValue('key1')).toBeUndefined();
});
it('注册插件,没有填写 pluginName默认值为 anonymous', async () => {
const mockFn = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
return {
name: 'xxx',
init: () => mockFn('anonymous'),
};
};
await pluginManager.register(creator2);
expect(pluginManager.get('anonymous')).toBeUndefined();
});
it('自定义/扩展 plugin context', async () => {
const mockFn = jest.fn();
const mockFn2 = jest.fn();
const creator2 = (ctx: IPublicModelPluginContext) => {
mockFn2(ctx);
return {
init: () => mockFn('anonymous'),
};
};
creator2.pluginName = 'yyy';
editor.set('enhancePluginContextHook', (originalContext) => {
originalContext.newProp = 1;
});
await pluginManager.register(creator2);
const [expectedCtx] = mockFn2.mock.calls[0];
expect(expectedCtx).toHaveProperty('newProp');
});
});

View File

@ -1,85 +0,0 @@
import '../fixtures/window';
import { isValidPreferenceKey, filterValidOptions } from '../../src/plugin/plugin-utils';
describe('plugin utils 测试', () => {
it('isValidPreferenceKey', () => {
expect(isValidPreferenceKey('x')).toBeFalsy();
expect(isValidPreferenceKey('x', { properties: {} })).toBeFalsy();
expect(isValidPreferenceKey('x', { properties: 1 })).toBeFalsy();
expect(isValidPreferenceKey('x', { properties: 'str' })).toBeFalsy();
expect(isValidPreferenceKey('x', { properties: [] })).toBeFalsy();
expect(
isValidPreferenceKey('x', {
title: 'title',
properties: [
{
key: 'y',
type: 'string',
description: 'x desc',
},
],
}),
).toBeFalsy();
expect(
isValidPreferenceKey('x', {
title: 'title',
properties: [
{
key: 'x',
type: 'string',
description: 'x desc',
},
],
}),
).toBeTruthy();
});
it('filterValidOptions', () => {
const mockDeclaration = {
title: 'title',
properties: [
{
key: 'x',
type: 'string',
description: 'x desc',
},
{
key: 'y',
type: 'string',
description: 'y desc',
},
{
key: 'z',
type: 'string',
description: 'z desc',
},
],
};
expect(filterValidOptions()).toBeUndefined();
expect(filterValidOptions(1)).toBe(1);
expect(filterValidOptions({
x: 1,
y: 2,
}, mockDeclaration)).toEqual({
x: 1,
y: 2,
});
expect(filterValidOptions({
x: 1,
y: undefined,
}, mockDeclaration)).toEqual({
x: 1,
});
expect(filterValidOptions({
x: 1,
z: null,
}, mockDeclaration)).toEqual({
x: 1,
});
expect(filterValidOptions({
a: 1,
}, mockDeclaration)).toEqual({
});
});
});

View File

@ -1,128 +0,0 @@
import sequencify, { sequence } from '../../src/plugin/sequencify';
describe('sequence', () => {
it('handles tasks with no dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] },
task2: { name: 'Task 2', dep: [] }
};
const results = [];
const missing = [];
const recursive = [];
sequence({ tasks, names: ['task1', 'task2'], results, missing, recursive, nest: [] });
expect(results).toEqual(['task1', 'task2']);
expect(missing).toEqual([]);
expect(recursive).toEqual([]);
});
it('correctly orders tasks based on dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] },
task2: { name: 'Task 2', dep: ['task1'] }
};
const results = [];
const missing = [];
const recursive = [];
sequence({ tasks, names: ['task2', 'task1'], results, missing, recursive, nest: [] });
expect(results).toEqual(['task1', 'task2']);
expect(missing).toEqual([]);
expect(recursive).toEqual([]);
});
it('identifies missing tasks', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] }
};
const results = [];
const missing = [];
const recursive = [];
const nest = []
sequence({ tasks, names: ['task2'], results, missing, recursive, nest });
expect(results).toEqual(['task2']);
expect(missing).toEqual(['task2']);
expect(recursive).toEqual([]);
expect(nest).toEqual([]);
});
it('detects recursive dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: ['task2'] },
task2: { name: 'Task 2', dep: ['task1'] }
};
const results = [];
const missing = [];
const recursive = [];
const nest = []
sequence({ tasks, names: ['task1', 'task2'], results, missing, recursive, nest });
expect(results).toEqual(['task1', 'task2', 'task1']);
expect(missing).toEqual([]);
expect(recursive).toEqual([['task1', 'task2', 'task1']]);
expect(nest).toEqual([]);
});
});
describe('sequence', () => {
it('should return tasks in sequence without dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] },
task2: { name: 'Task 2', dep: [] },
task3: { name: 'Task 3', dep: [] }
};
const names = ['task1', 'task2', 'task3'];
const expected = {
sequence: ['task1', 'task2', 'task3'],
missingTasks: [],
recursiveDependencies: []
};
expect(sequencify(tasks, names)).toEqual(expected);
});
it('should handle tasks with dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] },
task2: { name: 'Task 2', dep: ['task1'] },
task3: { name: 'Task 3', dep: ['task2'] }
};
const names = ['task3', 'task2', 'task1'];
const expected = {
sequence: ['task1', 'task2', 'task3'],
missingTasks: [],
recursiveDependencies: []
};
expect(sequencify(tasks, names)).toEqual(expected);
});
it('should identify missing tasks', () => {
const tasks = {
task1: { name: 'Task 1', dep: [] },
task2: { name: 'Task 2', dep: ['task3'] } // task3 is missing
};
const names = ['task1', 'task2'];
const expected = {
sequence: [],
missingTasks: ['task2.task3'],
recursiveDependencies: []
};
expect(sequencify(tasks, names)).toEqual(expected);
});
it('should detect recursive dependencies', () => {
const tasks = {
task1: { name: 'Task 1', dep: ['task2'] },
task2: { name: 'Task 2', dep: ['task1'] } // Recursive dependency
};
const names = ['task1', 'task2'];
const expected = {
sequence: [],
missingTasks: [],
recursiveDependencies: [['task1', 'task2', 'task1']]
};
expect(sequencify(tasks, names)).toEqual(expected);
});
});

View File

@ -1,181 +0,0 @@
import '../fixtures/window';
import { Editor } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import { Designer } from '../../src/designer/designer';
import formSchema from '../fixtures/schema/form';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
describe.only('Project 方法测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
beforeEach(() => {
editor = new Editor();
designer = new Designer({ editor, shellModelFactory });
project = designer.project;
doc = new DocumentModel(project, formSchema);
});
afterEach(() => {
project.unload();
designer.purge();
editor = null;
designer = null;
project = null;
});
it('simulator', () => {
const mockSimulator = { isSimulator: true, a: 1 };
project.mountSimulator(mockSimulator);
expect(project.simulator).toEqual(mockSimulator);
});
it('config / get / set', () => {
const mockConfig = { version: '1.0.0', componentsTree: [] };
project.config = mockConfig;
expect(project.config).toEqual(mockConfig);
const mockConfig2 = { version: '2.0.0', componentsTree: [] };
project.set('config', mockConfig2);
expect(project.get('config')).toEqual(mockConfig2);
project.set('version', '2.0.0');
expect(project.get('version')).toBe('2.0.0');
});
it('load', () => {
project.load({
componentsTree: [{
componentName: 'Page',
fileName: 'f1',
}],
}, 'f1');
expect(project.currentDocument?.fileName).toBe('f1');
});
it.skip('setSchema', () => {
project.load({
componentsTree: [{
componentName: 'Page',
fileName: 'f1',
}],
}, true);
project.setSchema({
componentsTree: [{
componentName: 'Page',
props: { a: 1 },
}],
});
expect(project.currentDocument?.rootNode?.propsData).toEqual({ a: 1 });
});
it('open / getDocument / checkExclusive', () => {
project.load({
componentsTree: [{
componentName: 'Page',
fileName: 'f1',
}],
});
const doc1 = project.createDocument({
componentName: 'Page',
fileName: 'f2',
});
const doc2 = project.createDocument({
componentName: 'Page',
fileName: 'f3',
});
project.open();
project.open('f2');
expect(project.currentDocument).toBe(doc1);
project.open('f3');
expect(project.currentDocument).toBe(doc2);
project.open('f1');
expect(project.currentDocument?.fileName).toBe('f1');
expect(project.open('not-existing')).toBeNull();
project.open(doc2);
expect(project.currentDocument).toBe(doc2);
const doc3 = project.open({
componentName: 'Page',
fileName: 'f4',
});
expect(project.currentDocument).toBe(doc3);
expect(project.documents.length).toBe(4);
expect(project.getDocument(project.currentDocument?.id)).toBe(doc3);
expect(project.getDocumentByFileName(project.currentDocument?.fileName)).toBe(doc3);
expect(project.getDocumentByFileName('unknown')).toBeNull();
expect(project.checkExclusive(project.currentDocument));
expect(project.documents[0].opened).toBeTruthy();
expect(project.documents[1].opened).toBeTruthy();
expect(project.documents[2].opened).toBeTruthy();
expect(project.documents[3].opened).toBeTruthy();
expect(project.documents[0].suspensed).toBeTruthy();
expect(project.documents[1].suspensed).toBeTruthy();
expect(project.documents[2].suspensed).toBeTruthy();
expect(project.documents[3].suspensed).toBeFalsy();
project.closeOthers(project.currentDocument);
expect(project.documents[0].opened).toBeFalsy();
expect(project.documents[1].opened).toBeFalsy();
expect(project.documents[2].opened).toBeFalsy();
expect(project.documents[3].opened).toBeTruthy();
expect(project.documents[0].suspensed).toBeTruthy();
expect(project.documents[1].suspensed).toBeTruthy();
expect(project.documents[2].suspensed).toBeTruthy();
expect(project.documents[3].suspensed).toBeFalsy();
});
it('removeDocument', () => {
const doc1 = project.createDocument({
componentName: 'Page',
fileName: 'f1',
});
project.removeDocument({});
expect(project.documents.length).toBe(1);
});
it('simulatorProps', () => {
designer._simulatorProps = { a: 1 };
expect(designer.simulatorProps.a).toBe(1);
designer._simulatorProps = () => ({ a: 1 });
expect(designer.simulatorProps.a).toBe(1);
});
it('onCurrentDocumentChange', () => {
const mockFn = jest.fn();
const off = project.onCurrentDocumentChange(mockFn);
project.open({
componentName: 'Page',
});
expect(mockFn).toHaveBeenCalled();
off();
mockFn.mockClear();
project.open({
componentName: 'Page',
});
expect(mockFn).not.toHaveBeenCalled();
});
it('setRendererReady / onRendererReady', () => {
const mockFn = jest.fn();
const off = project.onRendererReady(mockFn);
project.setRendererReady({ a: 1 });
expect(mockFn).toHaveBeenCalledWith({ a: 1 });
off();
mockFn.mockClear();
project.setRendererReady({ a: 1 });
expect(mockFn).not.toHaveBeenCalled();
});
});

View File

@ -1,305 +0,0 @@
import { set, cloneDeep } from 'lodash-es';
import '../fixtures/window';
import { Editor } from '@alilc/lowcode-editor-core';
import { Project } from '../../src/project/project';
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 { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) {
return props;
},
createSettingEntry: mockCreateSettingEntry,
postEvent() {},
};
}),
};
});
let designer = null;
beforeAll(() => {
designer = new Designer({});
designer.editor = new Editor();
});
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);
nodesMap.forEach((node) => {
// 触发 getter
node.settingEntry;
});
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
});
it('onSimulatorReady works', () => {
const project = new Project(designer, {
componentsTree: [formSchema],
});
project.open();
expect(project).toBeTruthy();
const mockCallback = jest.fn();
const removeListener = project.onSimulatorReady(mockCallback);
project.mountSimulator(undefined);
expect(mockCallback).toBeCalled();
removeListener();
});
it('open doc when doc is blank', () => {
const project = new Project(designer);
project.open();
expect(project).toBeTruthy();
const blankDoc = project.documents[0];
expect(blankDoc).toBeTruthy();
// 触发保存
blankDoc.history.savePoint();
expect(blankDoc.isModified()).toBeFalsy();
expect(blankDoc.isBlank()).toBeTruthy();
//二次打开doc会使用前面那个
const openedDoc = project.open();
expect(openedDoc).toBe(blankDoc);
});
it('load schema with autoOpen === true', () => {
const project = new Project(designer);
expect(project).toBeTruthy();
// trigger autoOpen case
project.load(
{
componentsTree: [formSchema],
},
true,
);
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);
nodesMap.forEach((node) => {
// 触发 getter
node.settingEntry;
});
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
});
it('load schema with autoOpen === true, and config contains layout.props.tabBar.item', () => {
const project = new Project(designer);
expect(project).toBeTruthy();
// trigger autoOpen case
project.load(
{
componentsTree: [
{
...formSchema,
fileName: 'demoFile1',
},
{
...formSchema,
fileName: 'demoFile2',
},
],
config: {
layout: {
props: {
tabBar: {
items: [
{
path: '/demoFile2',
},
],
},
},
},
},
},
true,
);
const { currentDocument } = project;
expect(currentDocument.fileName).toBe('demoFile2');
});
it('load schema with autoOpen === true', () => {
const project = new Project(designer);
expect(project).toBeTruthy();
// trigger autoOpen case
project.load(
{
componentsTree: [
{
...formSchema,
fileName: 'demoFile1',
},
{
...formSchema,
fileName: 'demoFile2',
},
],
},
'demoFile2',
);
const { currentDocument } = project;
expect(currentDocument.fileName).toBe('demoFile2');
});
it('setSchema works', () => {
const project = new Project(designer);
project.open();
expect(project).toBeTruthy();
project.setSchema({
componentsTree: [
{
...formSchema,
fileName: 'demoFile1',
},
],
});
const { currentDocument } = project;
expect(currentDocument.fileName).toBe('demoFile1');
});
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);
nodesMap.forEach((node) => {
// 触发 getter
node.settingEntry;
});
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);
});
it('get unknown document', () => {
const project = new Project(designer);
project.open(formSchema);
expect(project).toBeTruthy();
expect(project.getDocument('unknownId')).toBeNull();
});
it('get set i18n works', () => {
const project = new Project(designer);
project.open(formSchema);
expect(project).toBeTruthy();
project.i18n = formSchema.i18n;
expect(project.i18n).toStrictEqual(formSchema.i18n);
project.i18n = null;
expect(project.i18n).toStrictEqual({});
project.set('i18n', formSchema.i18n);
expect(project.get('i18n')).toStrictEqual(formSchema.i18n);
project.set('i18n', null);
expect(project.get('i18n')).toStrictEqual({});
});
});
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 测试', () => {});
});

View File

@ -1,8 +0,0 @@
// @ts-nocheck
import { invariant } from '../../src/utils/invariant';
it('invariant', () => {
expect(() => invariant(true)).not.toThrow();
expect(() => invariant(false, 'abc', 'xxx')).toThrow(/Invariant failed:/);
expect(() => invariant(false, 'abc')).toThrow(/Invariant failed:/);
});

View File

@ -1,164 +0,0 @@
// @ts-nocheck
import { isElementNode, isDOMNodeVisible, normalizeTriggers, makeEventsHandler } from '../../src/utils/misc';
it('isElementNode', () => {
expect(isElementNode(document.createElement('div'))).toBeTruthy();
expect(isElementNode(1)).toBeFalsy();
});
/**
* const domNodeRect = domNode.getBoundingClientRect();
const { width, height } = viewport.contentBounds;
const { left, right, top, bottom, width: nodeWidth, height: nodeHeight } = domNodeRect;
return (
left >= -nodeWidth &&
top >= -nodeHeight &&
bottom <= height + nodeHeight &&
right <= width + nodeWidth
);
*/
const genMockNode = ({ left, right, top, bottom, width, height }) => {
return { getBoundingClientRect: () => {
if (width === undefined || height === undefined) throw new Error('width and height is required.');
const base = { width, height };
let coordinate = {};
if (left !== undefined) {
coordinate = top !== undefined ? {
left,
right: left + width,
top,
bottom: top + height,
} : {
left,
right: left + width,
bottom,
top: bottom - height,
}
} else if (right !== undefined) {
coordinate = top !== undefined ? {
left: right - width,
right,
top,
bottom: top + height,
} : {
left: right - width,
right,
bottom,
top: bottom - height,
}
}
return { ...base, ...coordinate };
} };
};
const mockViewport = {
contentBounds: {
width: 300,
height: 300,
},
};
describe('isDOMNodeVisible', () => {
it('isDOMNodeVisible', () => {
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: 0,
top: 0,
}),
mockViewport,
),
).toBeTruthy();
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: -100,
top: 0,
}),
mockViewport,
),
).toBeTruthy();
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: 50,
top: 50,
}),
mockViewport,
),
).toBeTruthy();
// 左侧出界了
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: -101,
top: 0,
}),
mockViewport,
),
).toBeFalsy();
// 右侧出界了
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
right: 401,
top: 0,
}),
mockViewport,
),
).toBeFalsy();
// 上侧出界了
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: 50,
top: -101,
}),
mockViewport,
),
).toBeFalsy();
// 下侧出界了
expect(
isDOMNodeVisible(
genMockNode({
width: 100,
height: 100,
left: 50,
bottom: 401,
}),
mockViewport,
),
).toBeFalsy();
});
});
it('normalizeTriggers', () => {
expect(normalizeTriggers(['n', 'w'])).toEqual(['N', 'W']);
});
it('makeEventsHandler', () => {
const sensor = { contentDocument: document };
// no contentDocument
const sensor2 = {};
const bind = makeEventsHandler({ view: { document } } as any, [sensor, sensor2]);
const fn = jest.fn();
bind((doc) => fn(doc));
expect(fn).toHaveBeenCalledTimes(1);
});

View File

@ -1,43 +0,0 @@
// @ts-nocheck
import { includeSlot, removeSlot } from '../../src/utils/slot';
const genGetExtraProp = (val: string) => () => {
return {
getAsString() {
return val;
},
};
};
const remove = () => {};
const mockNode = {
slots: [{
getExtraProp: genGetExtraProp('haha'),
remove,
}, {
getExtraProp: genGetExtraProp('heihei'),
remove,
}]
};
// 没有 slots
const mockNode2 = {};
it('includeSlot', () => {
expect(includeSlot(mockNode, 'haha')).toBeTruthy();
expect(includeSlot(mockNode, 'heihei')).toBeTruthy();
expect(includeSlot(mockNode, 'xixi')).toBeFalsy();
expect(includeSlot(mockNode2, 'xixi')).toBeFalsy();
});
it('removeSlot', () => {
expect(removeSlot(mockNode, 'xixi')).toBeFalsy();
expect(mockNode.slots).toHaveLength(2);
expect(removeSlot(mockNode, 'haha')).toBeTruthy();
expect(mockNode.slots).toHaveLength(1);
expect(removeSlot(mockNode, 'heihei')).toBeTruthy();
expect(mockNode.slots).toHaveLength(0);
expect(removeSlot(mockNode2, 'xixi')).toBeFalsy();
});

View File

@ -1,111 +0,0 @@
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<string, Set<Function>> = new Map<string, Set<Function>>();
const mockRemoveAttribute = jest.fn();
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,
removeAttribute: mockRemoveAttribute,
};
});
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();
}
export function getMockElement(tagName, options = {}) {
const elem = document.createElement(tagName);
let {
width = 0,
height = 0,
top = 0,
bottom = 0,
left = 0,
right = 0,
} = options;
elem.getBoundingClientRect = () => {
return {
width,
height,
top,
bottom,
left,
right,
};
};
elem.setWidth = (newWidth) => {
width = newWidth;
};
elem.setHeight = (newHeight) => {
height = newHeight;
};
// console.log(elem.ownerDocument);
// elem.ownerDocument = document;
// elem.ownerDocument.defaultView = window;
return elem;
}

View File

@ -1,8 +0,0 @@
export function getMockEvent(target, options) {
return {
target,
preventDefault() {},
stopPropagation() {},
...options,
};
}

View File

@ -1,5 +0,0 @@
export { getIdsFromSchema, getNodeFromSchemaById } from '@alilc/lowcode-test-mate/es/utils';
export * from './bom';
export * from './event';
export * from './renderer';
export * from './misc';

View File

@ -1,25 +0,0 @@
import { set as lodashSet } from 'lodash-es';
export function set(obj: any, path: any, val: any) {
if (typeof path === 'string' && path.startsWith('prototype')) {
const segs = path.split('.');
let acc = obj;
segs.forEach((seg, idx) => {
if (idx !== segs.length - 1) {
acc[seg] = acc[seg] || {};
acc = acc[seg];
} else {
acc[seg] = val;
}
});
}
return lodashSet(obj, path, val);
}
export function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function delayObxTick() {
return delay(100);
}

View File

@ -1,8 +0,0 @@
export function getMockRenderer() {
return {
isSimulatorRenderer: true,
run() {
// console.log('renderer run');
},
};
}

View File

@ -0,0 +1,59 @@
import { ConfigurationModel } from '../../src';
import { describe, it, expect } from 'vitest';
describe('ConfigurationModel', () => {
it('should create an empty model', () => {
const model = ConfigurationModel.createEmptyModel();
expect(model.isEmpty()).toBe(true);
});
it('should add, set, and get values', () => {
const model = ConfigurationModel.createEmptyModel();
const key = 'testKey';
const value = 'testValue';
model.setValue(key, value);
expect(model.getValue(key)).toBe(value);
const newValue = 'newValue';
model.addValue(key, newValue);
expect(model.getValue(key)).toBe(newValue);
model.removeValue(key);
expect(model.getValue(key)).toBeUndefined();
});
it('should handle overrides', () => {
const model = ConfigurationModel.createEmptyModel();
const key = '[env].testKey';
const value = 'testValue';
model.setValue(key, value);
const overrides = model.overrides[0];
expect(overrides.keys).toContain('testKey');
expect(overrides.contents).toHaveProperty('testKey', value);
expect(overrides.identifiers).toContain('env');
model.removeValue(key);
expect(model.getValue(key)).toBeUndefined();
expect(model.overrides.length).toBe(0);
});
it('should create overrides correctly', () => {
const model = ConfigurationModel.createEmptyModel();
const baseKey = 'baseKey';
const baseValue = 'baseValue';
const overrideKey = '[environment].overrideKey';
const overrideValue = 'overrideValue';
// Set base and override values
model.setValue(baseKey, baseValue);
model.setValue(overrideKey, overrideValue);
// Override configuration model for a specific environment
const envModel = model.override('environment');
expect(envModel.getValue('overrideKey')).toBe(overrideValue);
expect(envModel.getValue(baseKey)).toBeUndefined();
expect(envModel.overrides).toEqual(model.overrides);
});
});

View File

@ -1,5 +1,6 @@
{ {
"name": "@ali/lowcode-engine-core", "name": "@alilc/lowcode-engine-core",
"version": "2.0.0-beta.0",
"description": "", "description": "",
"type": "module", "type": "module",
"main": "dist/engine-core.js", "main": "dist/engine-core.js",

View File

@ -0,0 +1,34 @@
import { type InstanceAccessor } from '@alilc/lowcode-shared';
export interface ICommandEvent {
commandId: string;
args: any[];
}
export interface ICommandHandler {
(accessor: InstanceAccessor, ...args: any[]): void;
}
export interface ICommand {
id: string;
handler: ICommandHandler;
metadata?: ICommandMetadata | null;
}
export interface ICommandMetadata {
/**
* A short summary of what the command does. This will be used in:
* - API commands
* - when showing keybindings that have no other UX
* - when searching for commands in the Command Palette
*/
readonly description: string;
readonly args?: ReadonlyArray<{
readonly name: string;
readonly isOptional?: boolean;
readonly description?: string;
// readonly constraint?: TypeConstraint;
// readonly schema?: IJSONSchema;
}>;
readonly returns?: string;
}

View File

@ -0,0 +1,115 @@
import {
type Event,
type EventDisposable,
type EventListener,
Emitter,
} from '@alilc/lowcode-shared';
import { ICommand, ICommandHandler } from './command';
import { Registry } from '../extension';
export type ICommandsMap = Map<string, ICommand>;
export interface ICommandRegistry {
onDidRegisterCommand: Event<string>;
registerCommand(id: string, command: ICommandHandler): EventDisposable;
registerCommand(command: ICommand): EventDisposable;
registerCommandAlias(oldId: string, newId: string): EventDisposable;
getCommand(id: string): ICommand | undefined;
getCommands(): ICommandsMap;
}
class CommandsRegistry implements ICommandRegistry {
private readonly _commands = new Map<string, LinkedList<ICommand>>();
private readonly _onDidRegisterCommand = new Emitter<string>();
onDidRegisterCommand(fn: EventListener<string>) {
return this._onDidRegisterCommand.on(fn);
}
registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): EventDisposable {
if (!idOrCommand) {
throw new Error(`invalid command`);
}
if (typeof idOrCommand === 'string') {
if (!handler) {
throw new Error(`invalid command`);
}
return this.registerCommand({ id: idOrCommand, handler });
}
// add argument validation if rich command metadata is provided
if (idOrCommand.metadata && Array.isArray(idOrCommand.metadata.args)) {
const constraints: Array<TypeConstraint | undefined> = [];
for (const arg of idOrCommand.metadata.args) {
constraints.push(arg.constraint);
}
const actualHandler = idOrCommand.handler;
idOrCommand.handler = function (accessor, ...args: any[]) {
validateConstraints(args, constraints);
return actualHandler(accessor, ...args);
};
}
// find a place to store the command
const { id } = idOrCommand;
let commands = this._commands.get(id);
if (!commands) {
commands = new LinkedList<ICommand>();
this._commands.set(id, commands);
}
const removeFn = commands.unshift(idOrCommand);
const ret = toDisposable(() => {
removeFn();
const command = this._commands.get(id);
if (command?.isEmpty()) {
this._commands.delete(id);
}
});
// tell the world about this command
this._onDidRegisterCommand.emit(id);
return ret;
}
registerCommandAlias(oldId: string, newId: string): IDisposable {
return this.registerCommand(oldId, (accessor, ...args) =>
accessor.get(ICommandService).executeCommand(newId, ...args),
);
}
getCommand(id: string): ICommand | undefined {
const list = this._commands.get(id);
if (!list || list.isEmpty()) {
return undefined;
}
return Iterable.first(list);
}
getCommands(): ICommandsMap {
const result = new Map<string, ICommand>();
for (const key of this._commands.keys()) {
const command = this.getCommand(key);
if (command) {
result.set(key, command);
}
}
return result;
}
}
const commandsRegistry = new CommandsRegistry();
export const Extension = {
command: 'base.contributions.command',
};
Registry.add(Extension.command, commandsRegistry);

View File

@ -0,0 +1,20 @@
import { createDecorator, Provide } from '@alilc/lowcode-shared';
import { Registry } from '../extension';
import { ICommandRegistry, Extension } from './commandRegistry';
export interface ICommandService {
executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T | undefined>;
}
export const ICommandService = createDecorator<ICommandService>('commandService');
@Provide(ICommandService)
export class CommandService implements ICommandService {
executeCommand<T = any>(id: string, ...args: any[]): Promise<T | undefined> {
const command = Registry.as<ICommandRegistry>(Extension.command).getCommand(id);
if (!command) {
return Promise.reject(new Error(`command '${id}' not found`));
}
}
}

View File

@ -0,0 +1,81 @@
import { type StringDictionary, Emitter, type EventListener } from '@alilc/lowcode-shared';
import { ConfigurationModel } from './configurationModel';
import {
type IConfigurationRegistry,
type IRegisteredConfigurationPropertySchema,
Extension,
} from './configurationRegistry';
import { Registry } from '../extension';
export interface IConfigurationOverrides {
overrideIdentifier?: string | null;
}
export interface IConfigurationUpdateOverrides {
overrideIdentifiers?: string[] | null;
}
export class DefaultConfiguration {
private emitter = new Emitter<{
defaults: ConfigurationModel;
properties: string[];
}>();
private _configurationModel = ConfigurationModel.createEmptyModel();
get configurationModel(): ConfigurationModel {
return this._configurationModel;
}
initialize(): ConfigurationModel {
this.resetConfigurationModel();
Registry.as<IConfigurationRegistry>(Extension.Configuration).onDidUpdateConfiguration(
({ properties }) => this.onDidUpdateConfiguration([...properties]),
);
return this.configurationModel;
}
reload(): ConfigurationModel {
this.resetConfigurationModel();
return this.configurationModel;
}
onDidChangeConfiguration(
listener: EventListener<[{ defaults: ConfigurationModel; properties: string[] }]>,
) {
return this.emitter.on(listener);
}
private onDidUpdateConfiguration(properties: string[]): void {
this.updateConfigurationModel(
properties,
Registry.as<IConfigurationRegistry>(Extension.Configuration).getConfigurationProperties(),
);
this.emitter.emit({ defaults: this.configurationModel, properties });
}
private resetConfigurationModel(): void {
this._configurationModel = ConfigurationModel.createEmptyModel();
const properties = Registry.as<IConfigurationRegistry>(
Extension.Configuration,
).getConfigurationProperties();
this.updateConfigurationModel(Object.keys(properties), properties);
}
private updateConfigurationModel(
properties: string[],
configurationProperties: StringDictionary<IRegisteredConfigurationPropertySchema>,
): void {
for (const key of properties) {
const propertySchema = configurationProperties[key];
if (propertySchema) {
this.configurationModel.setValue(key, propertySchema.default);
} else {
this.configurationModel.removeValue(key);
}
}
}
}

View File

@ -0,0 +1,406 @@
import { type StringDictionary } from '@alilc/lowcode-shared';
import { get as lodasgGet, isEqual, uniq, cloneDeep, isObject } from 'lodash-es';
import { OVERRIDE_PROPERTY_REGEX, overrideIdentifiersFromKey } from './configurationRegistry';
export type InspectValue<V> = {
readonly value?: V;
readonly override?: V;
readonly overrides?: { readonly identifiers: string[]; readonly value: V }[];
merged?: V;
};
export interface IConfigurationModel {
contents: any;
keys: string[];
overrides: IOverrides[];
}
export interface IOverrides {
keys: string[];
contents: any;
identifiers: string[];
}
/**
* model
*
*
*
* 使 [development] [production]
*
* case:
* const model = ConfigurationModel.createEmptyModel();
* const baseKey = 'baseKey';
* const baseValue = 'baseValue';
* const overrideKey = '[environment]';
* const overrideValue = { baseKey: 'overrideValue' };
*
* const envModel = model.override('environment');
* model.getValue(baseKey) === 'baseValue'
* envModel.getValue(baseKey) === 'overrideValue'
*/
export class ConfigurationModel implements IConfigurationModel {
static createEmptyModel(): ConfigurationModel {
return new ConfigurationModel({}, [], [], undefined);
}
private readonly overrideConfigurations = new Map<string, ConfigurationModel>();
constructor(
private readonly _contents: StringDictionary,
private readonly _keys: string[],
private readonly _overrides: IOverrides[],
public readonly raw?: ReadonlyArray<ConfigurationModel> | undefined,
) {}
get contents(): any {
return this._contents;
}
get overrides(): IOverrides[] {
return this._overrides;
}
get keys(): string[] {
return this._keys;
}
private _rawConfiguration: ConfigurationModel | undefined;
get rawConfiguration(): ConfigurationModel {
if (!this._rawConfiguration) {
if (this.raw?.length) {
const rawConfigurationModels = this.raw;
this._rawConfiguration = rawConfigurationModels.reduce(
(previous, current) => (current === previous ? current : previous.merge(current)),
rawConfigurationModels[0],
);
} else {
// raw is same as current
this._rawConfiguration = this;
}
}
return this._rawConfiguration;
}
toJSON(): IConfigurationModel {
return {
contents: this.contents,
overrides: this.overrides,
keys: this.keys,
};
}
inspect<V>(section?: string | undefined, overrideIdentifier?: string | null): InspectValue<V> {
const _this = this;
return {
get value() {
return _this.rawConfiguration.getValue<V>(section);
},
get override() {
return overrideIdentifier
? _this.rawConfiguration.getOverrideValue<V>(section, overrideIdentifier)
: undefined;
},
get merged() {
return overrideIdentifier
? _this.rawConfiguration.override(overrideIdentifier).getValue<V>(section)
: _this.rawConfiguration.getValue<V>(section);
},
get overrides() {
const overrides: { readonly identifiers: string[]; readonly value: V }[] = [];
for (const { contents, identifiers, keys } of _this.rawConfiguration.overrides) {
const value = new ConfigurationModel(contents, keys, [], undefined).getValue<V>(section);
if (value !== undefined) {
overrides.push({ identifiers, value });
}
}
return overrides.length ? overrides : undefined;
},
};
}
merge(...others: ConfigurationModel[]): ConfigurationModel {
const contents = cloneDeep(this.contents);
const overrides = cloneDeep(this.overrides);
const keys = [...this.keys];
const raws = this.raw?.length ? [...this.raw] : [this];
for (const other of others) {
raws.push(...(other.raw?.length ? other.raw : [other]));
if (other.isEmpty()) {
continue;
}
this.mergeContents(contents, other.contents);
for (const otherOverride of other.overrides) {
const [override] = overrides.filter((o) =>
isEqual(o.identifiers, otherOverride.identifiers),
);
if (override) {
this.mergeContents(override.contents, otherOverride.contents);
override.keys.push(...otherOverride.keys);
override.keys = uniq(override.keys);
} else {
overrides.push(cloneDeep(otherOverride));
}
}
for (const key of other.keys) {
if (keys.indexOf(key) === -1) {
keys.push(key);
}
}
}
return new ConfigurationModel(contents, keys, overrides, raws);
}
override(identifier: string): ConfigurationModel {
let overrideConfigurationModel = this.overrideConfigurations.get(identifier);
if (!overrideConfigurationModel) {
overrideConfigurationModel = this.createOverrideConfigurationModel(identifier);
this.overrideConfigurations.set(identifier, overrideConfigurationModel);
}
return overrideConfigurationModel;
}
private createOverrideConfigurationModel(identifier: string): ConfigurationModel {
const overrideContents = this.getContentsForOverrideIdentifer(identifier);
if (
!overrideContents ||
typeof overrideContents !== 'object' ||
!Object.keys(overrideContents).length
) {
// If there are no valid overrides, return self
return this;
}
const contents: any = {};
for (const key of uniq([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {
let contentsForKey = this.contents[key];
const overrideContentsForKey = overrideContents[key];
// If there are override contents for the key, clone and merge otherwise use base contents
if (overrideContentsForKey) {
// Clone and merge only if base contents and override contents are of type object otherwise just override
if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {
contentsForKey = cloneDeep(contentsForKey);
this.mergeContents(contentsForKey, overrideContentsForKey);
} else {
contentsForKey = overrideContentsForKey;
}
}
contents[key] = contentsForKey;
}
return new ConfigurationModel(contents, this.keys, this.overrides);
}
private getContentsForOverrideIdentifer(identifier: string): any {
let contentsForIdentifierOnly: StringDictionary | null = null;
let contents: StringDictionary | null = null;
const mergeContents = (contentsToMerge: any) => {
if (contentsToMerge) {
if (contents) {
this.mergeContents(contents, contentsToMerge);
} else {
contents = cloneDeep(contentsToMerge);
}
}
};
for (const override of this.overrides) {
if (override.identifiers.length === 1 && override.identifiers[0] === identifier) {
contentsForIdentifierOnly = override.contents;
} else if (override.identifiers.includes(identifier)) {
mergeContents(override.contents);
}
}
// Merge contents of the identifier only at the end to take precedence.
mergeContents(contentsForIdentifierOnly);
return contents;
}
private mergeContents(source: any, target: any): void {
for (const key of Object.keys(target)) {
if (key in source) {
if (isObject(source[key]) && isObject(target[key])) {
this.mergeContents(source[key], target[key]);
continue;
}
}
source[key] = cloneDeep(target[key]);
}
}
isEmpty(): boolean {
return (
this._keys.length === 0 &&
Object.keys(this._contents).length === 0 &&
this._overrides.length === 0
);
}
getValue<V>(section?: string | undefined): V {
return section ? lodasgGet(this.contents, section) : this.contents;
}
// Update methods
addValue(key: string, value: any): void {
this.updateValue(key, value, true);
}
setValue(key: string, value: any): void {
this.updateValue(key, value, false);
}
removeValue(key: string): void {
const index = this.keys.indexOf(key);
if (index !== -1) {
this.keys.splice(index, 1);
removeFromValueTree(this.contents, key);
}
const isOverrideKey = OVERRIDE_PROPERTY_REGEX.test(key);
if (isOverrideKey) {
const identifiers = overrideIdentifiersFromKey(key);
const overrideIndex = this.overrides.findIndex((o) => isEqual(o.identifiers, identifiers));
if (overrideIndex !== -1) {
const override = this.overrides[overrideIndex];
removeFromValueTree(override.contents, key);
if (Object.keys(override.contents).length === 0) {
this.overrides.splice(overrideIndex, 1);
} else {
override.keys = Object.keys(override.contents);
}
}
}
}
private updateValue(key: string, value: any, add: boolean): void {
addToValueTree(this.contents, key, value);
if (add || !this.keys.includes(key)) {
this.keys.push(key);
}
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
const identifiers = overrideIdentifiersFromKey(key);
const override = this.overrides.find((o) => isEqual(o.identifiers, identifiers));
if (override) {
addToValueTree(override.contents, key, value);
} else {
this.overrides.push({
identifiers,
keys: Object.keys(this.contents[key]),
contents: toValuesTree(this.contents[key]),
});
}
}
}
getOverrideValue<V>(section: string | undefined, overrideIdentifier: string): V | undefined {
const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier);
return overrideContents
? section
? lodasgGet(overrideContents, section)
: overrideContents
: undefined;
}
getKeysForOverrideIdentifier(identifier: string): string[] {
const keys: string[] = [];
for (const override of this.overrides) {
if (override.identifiers.includes(identifier)) {
keys.push(...override.keys);
}
}
return uniq(keys);
}
getAllOverrideIdentifiers(): string[] {
const result: string[] = [];
for (const override of this.overrides) {
result.push(...override.identifiers);
}
return uniq(result);
}
}
function removeFromValueTree(valueTree: any, key: string): void {
const segments = key.split('.');
doRemoveFromValueTree(valueTree, segments);
}
function doRemoveFromValueTree(valueTree: any, segments: string[]): void {
const first = segments.shift()!;
if (segments.length === 0) {
// Reached last segment
delete valueTree[first];
return;
}
if (Object.keys(valueTree).includes(first)) {
const value = valueTree[first];
if (typeof value === 'object' && !Array.isArray(value)) {
doRemoveFromValueTree(value, segments);
if (Object.keys(value).length === 0) {
delete valueTree[first];
}
}
}
}
function addToValueTree(
settingsTreeRoot: any,
key: string,
value: any,
conflictReporter: (message: string) => void = console.error,
): void {
const segments = key.split('.');
const last = segments.pop()!;
let curr = settingsTreeRoot;
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
let obj = curr[s];
switch (typeof obj) {
case 'undefined':
obj = curr[s] = Object.create(null);
break;
case 'object':
if (obj === null) {
conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is null`);
return;
}
break;
default:
conflictReporter(
`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`,
);
return;
}
curr = obj;
}
if (typeof curr === 'object' && curr !== null) {
try {
curr[last] = value; // workaround https://github.com/microsoft/vscode/issues/13606
} catch (e) {
conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);
}
} else {
conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);
}
}
function toValuesTree(properties: StringDictionary): any {
const root = Object.create(null);
for (const key in properties) {
addToValueTree(root, key, properties[key]);
}
return root;
}

View File

@ -0,0 +1,301 @@
import {
type Event,
Emitter,
type StringDictionary,
type JSONValueType,
jsonTypes,
} from '@alilc/lowcode-shared';
import { uniq, isUndefined } from 'lodash-es';
import { Registry } from '../extension/registry';
const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`;
const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g');
export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`;
export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN);
export function overrideIdentifiersFromKey(key: string): string[] {
const identifiers: string[] = [];
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
while (matches?.length) {
const identifier = matches[1].trim();
if (identifier) {
identifiers.push(identifier);
}
matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
}
}
return uniq(identifiers);
}
export interface IConfigurationRegistry {
/**
* Register a configuration to the registry.
*/
registerConfiguration(configuration: IConfigurationNode, validate?: boolean): void;
/**
* Register multiple configurations to the registry.
*/
registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void;
/**
* Deregister multiple configurations from the registry.
*/
deregisterConfigurations(configurations: IConfigurationNode[]): void;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
* Property or default value changes are not allowed.
*/
notifyConfigurationSchemaUpdated(): void;
/**
* Event that fires whenever a configuration has been
* registered.
*/
readonly onDidSchemaChange: Event<void>;
/**
* Event that fires whenever a configuration has been
* registered.
*/
readonly onDidUpdateConfiguration: Event<{
properties: ReadonlySet<string>;
defaultsOverrides?: boolean;
}>;
/**
* Returns all configuration nodes contributed to this registry.
*/
getConfigurations(): IConfigurationNode[];
/**
* Returns all configurations settings of all configuration nodes contributed to this registry.
*/
getConfigurationProperties(): StringDictionary<IRegisteredConfigurationPropertySchema>;
/**
* Returns all excluded configurations settings of all configuration nodes contributed to this registry.
*/
getExcludedConfigurationProperties(): StringDictionary<IRegisteredConfigurationPropertySchema>;
}
export interface IConfigurationNode {
id?: string;
order?: number;
type?: JSONValueType | JSONValueType[];
title?: string;
description?: string;
properties?: StringDictionary<IConfigurationPropertySchema>;
allOf?: IConfigurationNode[];
extensionInfo?: IExtensionInfo;
}
export interface IConfigurationPropertySchema {
type?: JSONValueType;
default?: any;
tags?: string[];
included?: boolean;
deprecated?: boolean;
deprecationMessage?: string;
}
export interface IExtensionInfo {
id: string;
displayName?: string;
}
export type ConfigurationDefaultValueSource = IExtensionInfo | Map<string, IExtensionInfo>;
export interface IConfigurationDefaults {
overrides: StringDictionary;
source?: IExtensionInfo;
}
export interface IRegisteredConfigurationPropertySchema extends IConfigurationPropertySchema {
source?: IExtensionInfo; // Source of the Property
defaultValueSource?: ConfigurationDefaultValueSource; // Source of the Default Value
}
export class ConfigurationRegistry implements IConfigurationRegistry {
private configurationContributors: IConfigurationNode[];
private configurationProperties: StringDictionary<IRegisteredConfigurationPropertySchema>;
private excludedConfigurationProperties: StringDictionary<IRegisteredConfigurationPropertySchema>;
private schemaChangeEmitter = new Emitter<void>();
private updateConfigurationEmitter = new Emitter<{
properties: ReadonlySet<string>;
defaultsOverrides?: boolean;
}>();
constructor() {
this.configurationContributors = [];
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
}
registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void {
this.registerConfigurations([configuration], validate);
}
registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void {
const properties = new Set<string>();
this.doRegisterConfigurations(configurations, validate, properties);
this.schemaChangeEmitter.emit();
this.updateConfigurationEmitter.emit({ properties });
}
private doRegisterConfigurations(
configurations: IConfigurationNode[],
validate: boolean,
bucket: Set<string>,
): void {
configurations.forEach((configuration) => {
this.validateAndRegisterProperties(
configuration,
validate,
configuration.extensionInfo,
bucket,
);
this.configurationContributors.push(configuration);
});
}
private validateAndRegisterProperties(
configuration: IConfigurationNode,
validate: boolean = true,
extensionInfo: IExtensionInfo | undefined,
bucket: Set<string>,
): void {
const properties = configuration.properties;
if (properties) {
for (const key in properties) {
const property: IRegisteredConfigurationPropertySchema = properties[key];
if (validate && this.validateProperty(key)) {
continue;
}
property.source = extensionInfo;
// update default value
this.updatePropertyDefaultValue(property);
// Add to properties maps
// Property is included by default if 'included' is unspecified
if (
Object.prototype.hasOwnProperty.call(properties[key], 'included') &&
!properties[key].included
) {
this.excludedConfigurationProperties[key] = properties[key];
continue;
}
this.configurationProperties[key] = properties[key];
bucket.add(key);
}
const subNodes = configuration.allOf;
if (subNodes) {
for (const node of subNodes) {
this.validateAndRegisterProperties(node, validate, extensionInfo, bucket);
}
}
}
}
private validateProperty(property: string): string | null {
if (!property.trim()) {
return 'Cannot register an empty property';
}
if (OVERRIDE_PROPERTY_REGEX.test(property)) {
return `Cannot register ${property}. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.`;
}
if (this.configurationProperties[property] !== undefined) {
return `Cannot register ${property}. This property is already registered.`;
}
return null;
}
private updatePropertyDefaultValue(property: IRegisteredConfigurationPropertySchema): void {
let defaultValue = undefined;
let defaultSource = undefined;
if (isUndefined(defaultValue)) {
defaultValue = property.default;
defaultSource = undefined;
}
if (isUndefined(defaultValue)) {
defaultValue = jsonTypes.getDefaultValue(property.type);
}
property.default = defaultValue;
property.defaultValueSource = defaultSource;
}
deregisterConfigurations(configurations: IConfigurationNode[]): void {
const properties = new Set<string>();
this.doDeregisterConfigurations(configurations, properties);
this.schemaChangeEmitter.emit();
this.updateConfigurationEmitter.emit({ properties });
}
private doDeregisterConfigurations(
configurations: IConfigurationNode[],
bucket: Set<string>,
): void {
const deregisterConfiguration = (configuration: IConfigurationNode) => {
if (configuration.properties) {
for (const key in configuration.properties) {
bucket.add(key);
delete this.configurationProperties[key];
}
}
configuration.allOf?.forEach((node) => deregisterConfiguration(node));
};
for (const configuration of configurations) {
deregisterConfiguration(configuration);
const index = this.configurationContributors.indexOf(configuration);
if (index !== -1) {
this.configurationContributors.splice(index, 1);
}
}
}
notifyConfigurationSchemaUpdated(): void {
this.schemaChangeEmitter.emit();
}
getConfigurationProperties(): StringDictionary<IRegisteredConfigurationPropertySchema> {
return this.configurationProperties;
}
getConfigurations(): IConfigurationNode[] {
return this.configurationContributors;
}
getExcludedConfigurationProperties(): StringDictionary<IRegisteredConfigurationPropertySchema> {
return this.excludedConfigurationProperties;
}
onDidUpdateConfiguration(
fn: (change: {
properties: ReadonlySet<string>;
defaultsOverrides?: boolean | undefined;
}) => void,
) {
return this.updateConfigurationEmitter.on(fn);
}
onDidSchemaChange(fn: () => void) {
return this.schemaChangeEmitter.on(fn);
}
}
export const Extension = {
Configuration: 'base.contributions.configuration',
};
Registry.add(Extension.Configuration, new ConfigurationRegistry());

View File

@ -0,0 +1,63 @@
import { createDecorator, Provide, type Event } from '@alilc/lowcode-shared';
import { IConfigurationOverrides, IConfigurationUpdateOverrides } from './configuration';
export interface IConfigurationChangeEvent {
readonly affectedKeys: ReadonlySet<string>;
readonly change: IConfigurationChange;
affectsConfiguration(configuration: string, overrides?: string[]): boolean;
}
export interface IConfigurationChange {
keys: string[];
overrides: [string, string[]][];
}
export interface IConfigurationService {
/**
* Fetches the value of the section for the given overrides.
* Value can be of native type or an object keyed off the section name.
*
* @param section - Section of the configuration. Can be `null` or `undefined`.
* @param overrides - Overrides that has to be applied while fetching
*
*/
getValue<T>(): T;
getValue<T>(section: string): T;
getValue<T>(overrides: IConfigurationOverrides): T;
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
/**
* Update a configuration value.
*
* Use `overrides` to update the configuration for a resource or for override identifiers or both.
*
* Passing a resource through overrides will update the configuration in the workspace folder containing that resource.
*
* *Note 1:* Updating configuration to a default value will remove the configuration from the requested target. If not target is passed, it will be removed from all writeable targets.
*
* *Note 2:* Use `undefined` value to remove the configuration from the given target. If not target is passed, it will be removed from all writeable targets.
*
* @param key setting to be updated
* @param value The new value
*/
updateValue(key: string, value: any): Promise<void>;
updateValue(
key: string,
value: any,
overrides: IConfigurationOverrides | IConfigurationUpdateOverrides,
): Promise<void>;
inspect<T>(key: string, overrides?: IConfigurationOverrides): Readonly<T>;
reloadConfiguration(): Promise<void>;
keys(): string[];
onDidChangeConfiguration: Event<IConfigurationChangeEvent>;
}
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
@Provide(IConfigurationService)
export class ConfigurationService implements IConfigurationService {}

View File

@ -0,0 +1,3 @@
export * from './configurationModel';
export * from './configurationRegistry';
export * from './configuration';

Some files were not shown because too many files have changed in this diff Show More