refactor: 兼容 propTypes 写错

refactor(test): 增加 dragon / host / designer 部分单测
This commit is contained in:
力皓 2020-12-17 22:26:35 +08:00
parent 4cf6637c4a
commit 084c342a65
15 changed files with 1108 additions and 82 deletions

View File

@ -103,7 +103,7 @@ LowcodeTypes.exact = (typesMap: any) => {
const configs = Object.keys(typesMap).map(key => {
return {
name: key,
propType: typesMap[key].lowcodeType || 'any',
propType: typesMap[key]?.lowcodeType || 'any',
};
});
return define(PropTypes.exact(typesMap), {
@ -117,7 +117,7 @@ LowcodeTypes.shape = (typesMap: any) => {
const configs = Object.keys(typesMap).map(key => {
return {
name: key,
propType: typesMap[key].lowcodeType || 'any',
propType: typesMap[key]?.lowcodeType || 'any',
};
});
return define(PropTypes.shape(typesMap), {

View File

@ -8,7 +8,6 @@ function isInLiveEditing() {
if (globalContext.has(Editor)) {
return Boolean(globalContext.get(Editor).get('designer')?.project?.simulator?.liveEditing?.editing);
}
return false;
}
function getNextForSelect(next: any, head?: any, parent?: any): any {

View File

@ -2,7 +2,7 @@ import React from 'react';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../fixtures/window';
import { Editor } from '@ali/lowcode-editor-core';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import {
AssetLevel,
Asset,
@ -14,65 +14,100 @@ import {
import { Project } from '../../src/project/project';
import { Node } from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer';
import { DocumentModel } from '../../src/document/document-model';
import formSchema from '../fixtures/schema/form';
import { getMockDocument, getMockWindow, getMockEvent } from '../utils';
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
import { eq } from 'lodash';
import { fireEvent } from '@testing-library/react';
const editor = new Editor();
describe('host 测试', () => {
let editor: Editor;
let designer: Designer;
beforeEach(() => {
designer = new Designer({ editor });
});
afterEach(() => {
designer._componentMetasMap.clear();
designer = null;
let project: Project;
let doc: DocumentModel;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
it('基础方法测试', async () => {
const host = new BuiltinSimulatorHost(designer.project);
expect(host.currentDocument).toBe(designer.project.currentDocument);
expect(host.renderEnv).toBe('default');
expect(host.device).toBe('default');
expect(host.deviceClassName).toBeUndefined();
host.setProps({
renderEnv: 'rax',
device: 'mobile',
deviceClassName: 'mobile-rocks',
componentsAsset: [{
beforeEach(() => {
designer = new Designer({ editor });
project = designer.project;
doc = project.createDocument(formSchema);
});
afterEach(() => {
project.unload();
project.mountSimulator(undefined);
designer._componentMetasMap.clear();
designer.purge();
designer = null;
project = null;
});
describe('基础方法测试', () => {
it('setProps / get / set', async () => {
const host = new BuiltinSimulatorHost(designer.project);
expect(host.currentDocument).toBe(designer.project.currentDocument);
expect(host.renderEnv).toBe('default');
expect(host.device).toBe('default');
expect(host.deviceClassName).toBeUndefined();
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',
}],
theme: {
}]);
expect(host.theme).toEqual({
type: AssetType.CSSText,
content: '.theme {font-size: 50px;}',
}
});
expect(host.renderEnv).toBe('rax');
expect(host.device).toBe('mobile');
expect(host.deviceClassName).toBe('mobile-rocks');
expect(host.componentsAsset).toEqual([{
type: AssetType.JSText,
content: 'console.log(1)',
}, {
type: AssetType.JSUrl,
content: '//path/to/js',
}]);
expect(host.theme).toEqual({
type: AssetType.CSSText,
content: '.theme {font-size: 50px;}',
});
expect(host.componentsMap).toBe(designer.componentsMap);
});
expect(host.componentsMap).toBe(designer.componentsMap);
expect(host.requestHandlersMap).toEqual({});
host.set('renderEnv', 'vue');
expect(host.renderEnv).toBe('vue');
host.set('renderEnv', 'vue');
expect(host.renderEnv).toBe('vue');
expect(host.getComponentContext).toThrow('Method not implemented.');
expect(host.getComponentContext).toThrow('Method not implemented.');
});
it('connect', () => {});
it('mountViewport', () => {});
it('mountContentFrame', () => {});
it('autorun', () => {});
it('purge', () => {});
});
describe('事件测试', () => {
it('setupDragAndClick', () => {
});
});
it('事件测试', async () => {

View File

@ -0,0 +1,60 @@
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,5 +1,5 @@
import '../fixtures/window';
import { parseMetadata } from '../../src/builtin-simulator/utils/parse-metadata';
import '../../fixtures/window';
import { parseMetadata } from '../../../src/builtin-simulator/utils/parse-metadata';
describe('parseMetadata', () => {
it('parseMetadata', async () => {

View File

@ -7,7 +7,7 @@ import {
removeVersion,
resolveAbsoluatePath,
joinPath,
} from '../../src/builtin-simulator/utils/path';
} from '../../../src/builtin-simulator/utils/path';
describe('builtin-simulator/utils/path 测试', () => {
it('isPackagePath', () => {

View File

@ -1,5 +1,5 @@
import '../fixtures/disable-raf';
import { throttle } from '../../src/builtin-simulator/utils/throttle';
import '../../fixtures/disable-raf';
import { throttle } from '../../../src/builtin-simulator/utils/throttle';
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

View File

@ -0,0 +1,180 @@
import '../fixtures/window';
import { getMockWindow, set } from '../utils';
import { Editor, globalContext } from '@ali/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 { fireEvent } from '@testing-library/react';
import { getMockElement, delay } from '../utils';
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 });
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

@ -0,0 +1,40 @@
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,3 +1,4 @@
jest.mock('@ali/lowcode-utils');
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../fixtures/window';
@ -6,6 +7,8 @@ import { Designer } from '../../src/designer/designer';
import { Project } from '../../src/project/project';
import formSchema from '../fixtures/schema/form';
import '../../src/designer/builtin-hotkey';
import { fireEvent } from '@testing-library/react';
import { isFormEvent } from '@ali/lowcode-utils';
const editor = new Editor();
@ -28,8 +31,7 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbj')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 39 });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 39 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy();
});
@ -38,8 +40,7 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 37 });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 37 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbj')).toBeTruthy();
});
@ -48,8 +49,7 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 40 });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 40 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbo')).toBeTruthy();
});
@ -58,8 +58,7 @@ describe('快捷键测试', () => {
const secondCardNode = designer.currentDocument?.getNode('node_k1ow3cbm')!;
secondCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 38 });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 38 });
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy();
});
@ -69,8 +68,7 @@ describe('快捷键测试', () => {
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
firstButtonNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 39, altKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 39, altKey: true });
expect(firstButtonNode.prevSibling?.getId()).toBe('node_k1ow3cbp');
});
@ -80,8 +78,7 @@ describe('快捷键测试', () => {
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
secondButtonNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 37, altKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 37, altKey: true });
expect(secondButtonNode.nextSibling?.getId()).toBe('node_k1ow3cbn');
});
@ -91,8 +88,7 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 38, altKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 38, altKey: true });
});
// 将节点移入到兄弟节点中
@ -100,8 +96,7 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 40, altKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 40, altKey: true });
});
// 撤销
@ -114,8 +109,7 @@ describe('快捷键测试', () => {
await new Promise(resolve => setTimeout(resolve, 1000));
let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
@ -132,8 +126,7 @@ describe('快捷键测试', () => {
await new Promise(resolve => setTimeout(resolve, 1000));
let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 90, metaKey: true });
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
@ -141,8 +134,7 @@ describe('快捷键测试', () => {
await new Promise(resolve => setTimeout(resolve, 1000));
event = new KeyboardEvent('keydown', { keyCode: 89, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 89, metaKey: true });
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
@ -153,19 +145,16 @@ describe('快捷键测试', () => {
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
firstCardNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
});
it('command + v', async () => {
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
secondButtonNode.select();
let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 67, metaKey: true });
event = new KeyboardEvent('keydown', { keyCode: 86, metaKey: true });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 86, metaKey: true });
await new Promise(resolve => setTimeout(resolve, 1000));
@ -180,8 +169,7 @@ describe('快捷键测试', () => {
expect(designer.currentSelection!.selected.includes('node_k1ow3cbp')).toBeTruthy();
let event = new KeyboardEvent('keydown', { keyCode: 27 });
document.dispatchEvent(event);
fireEvent.keyDown(document, { keyCode: 27 });
expect(designer.currentSelection!.selected.length).toBe(0);
});
@ -194,9 +182,110 @@ describe('快捷键测试', () => {
expect(secondButtonNode.prevSibling.id).toBe('node_k1ow3cbn');
let event = new KeyboardEvent('keydown', { keyCode: 46 });
document.dispatchEvent(event);
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', () => {
designer.currentDocument?.selection.select('page');
// nothing happened
isFormEvent.mockReturnValue(true);
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');
});
});
});

View File

@ -0,0 +1,404 @@
import '../fixtures/window';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { Project } from '../../src/project/project';
import { DocumentModel } from '../../src/document/document-model';
import { Designer } from '../../src/designer/designer';
import { Dragon, DragObjectType } 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';
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 });
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,
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;
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,
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;
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 }, TransformStage.Init)).toEqual({ num: 1 });
// props 是数组
expect(designer.transformProps([{ num: 1 }], TransformStage.Init)).toEqual([{ num: 1 }]);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Init);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Init);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Clone);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Serilize);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Render);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Save);
designer.addPropsReducer((props, node) => {
props.num = props.num + 1;
return props;
}, TransformStage.Upgrade);
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Init)).toEqual({ num: 3 });
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Clone)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Serilize)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Render)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Save)).toEqual({ num: 2 });
expect(designer.transformProps({ num: 1 }, {}, TransformStage.Upgrade)).toEqual({ num: 2 });
designer.addPropsReducer((props, node) => {
throw new Error('calculate error');
}, TransformStage.Upgrade);
expect(designer.transformProps({ num: 1 }, {}, 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, ...initialProps });
expect(designer.simulatorComponent).toEqual({ isSimulatorComp: true });
expect(designer.simulatorProps).toEqual({ designMode: 'design' });
expect(designer.suspensed).toBeTruthy();
expect(designer._componentMetasMap.has('Div')).toBeTruthy();
expect(designer._componentMetasMap.has('Button')).toBeTruthy();
const { editor: editorFromDesigner, ...others } = designer.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._componentMetasMap.has('Button')).toBeTruthy();
expect(designer._componentMetasMap.has('Div')).toBeTruthy();
const { editor: editorFromDesigner2, ...others2 } = designer.props;
expect(others2).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('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);
});
});

View File

@ -0,0 +1,22 @@
import { Detecting } from '../../src/designer/detecting';
it('Detecting 测试', () => {
const detecting = new Detecting();
expect(detecting.enable).toBeTruthy();
const mockNode = { document };
detecting.capture(mockNode);
expect(detecting.current).toBe(mockNode);
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

@ -30,7 +30,6 @@ import formMetadata from '../fixtures/component-metadata/form';
import otherMeta from '../fixtures/component-metadata/other';
import pageMetadata from '../fixtures/component-metadata/page';
import { fireEvent } from '@testing-library/react';
import { clearScreenDown } from 'readline';
describe('Dragon 测试', () => {
let editor: Editor;

View File

@ -0,0 +1,196 @@
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' : 'row';
}
}
})
.mockImplementationOnce(() => {
return {
getPropertyValue: (pName) => {
return pName === 'display' ? 'flex' : 'column';
}
}
});
expect(isRowContainer(getMockElement('div'))).toBeFalsy();
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();
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);
});

View File

@ -14,6 +14,7 @@ interface MockDocument extends Document {
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]));
@ -45,6 +46,7 @@ const mockCreateElement = jest.fn((tagName) => {
addEventListener: mockAddEventListener,
removeEventListener: mockRemoveEventListener,
triggerEventListener: mockTriggerEventListener,
removeAttribute: mockRemoveAttribute,
}
})