mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 21:12:53 +00:00
495 lines
16 KiB
TypeScript
495 lines
16 KiB
TypeScript
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 } 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 { DragObjectType } 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();
|
||
!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('DragObjectType.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: DragObjectType.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() {},
|
||
},
|
||
};
|
||
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('DragObjectType.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: DragObjectType.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() {},
|
||
},
|
||
};
|
||
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, TransformStage.Init)).toEqual({ num: 1 });
|
||
// props 是数组
|
||
expect(designer.transformProps([{ num: 1 }], mockNode, TransformStage.Init)).toEqual([{ num: 1 }]);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Init);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Init);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Clone);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Serilize);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Render);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Save);
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
props.num += 1;
|
||
return props;
|
||
}, TransformStage.Upgrade);
|
||
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Init)).toEqual({ num: 3 });
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Clone)).toEqual({ num: 2 });
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Serilize)).toEqual({ num: 2 });
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Render)).toEqual({ num: 2 });
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Save)).toEqual({ num: 2 });
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.Upgrade)).toEqual({ num: 2 });
|
||
|
||
designer.addPropsReducer((props, node) => {
|
||
throw new Error('calculate error');
|
||
}, TransformStage.Upgrade);
|
||
expect(designer.transformProps({ num: 1 }, mockNode, TransformStage.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() {},
|
||
},
|
||
};
|
||
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() {},
|
||
},
|
||
},
|
||
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);
|
||
});
|
||
});
|