Merge branch 'release/1.0.26' into release/0.9.54

This commit is contained in:
荣彬 2020-12-22 13:49:01 +08:00
commit 031c89b81d
84 changed files with 3563 additions and 459 deletions

View File

@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* rax perf ([3abe2ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3abe2ab))
* requestHandlersMap 没有加到 appContext 里 ([a8d43c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8d43c3))
* simulator-renderer 补充丢失代码 ([67dd7e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/67dd7e2))
* 传递正确的 removeIndex 给到 subtreeModified 钩子 ([822b2fd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/822b2fd))
* 修复 overridePropsConfigure 参数为数组时的逻辑 ([4e58e09](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4e58e09))
* 修复组件不会插入到选中节点之内或者之后的逻辑 ([93b005b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93b005b))
### Features
* 支持 build sourceMap, 方便用户调试 ([6bf75cd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6bf75cd))
* 支持用户修改 builtinComponentActions ([bc183d1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc183d1))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复 prop 无法删除最后一个 item ([e18a386](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e18a386))
* 修复大纲树和组件面板来回点击异常 ([8b9a6ec](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b9a6ec))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"lerna": "2.11.0",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"npmClient": "tyarn",
"registry": "http://registry.npm.alibaba-inc.com",
"useWorkspaces": true,
@ -13,6 +13,14 @@
"--no-package-lock"
]
},
"version": {
"allowBranch": [
"master",
"main",
"release/*",
"daily/*"
]
},
"publish": {
"npmClient": "tnpm",
"verifyRegistry": false,

View File

@ -15,7 +15,8 @@
"lint": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet",
"lint:fix": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet --fix",
"pub": "lerna publish --force-publish --cd-version patch --message \"chore(release): publish %v\"",
"pubbeta": "lerna publish --force-publish --cd-version prerelease --npm-tag beta --preid beta --message \"chore(release): publish %v\"",
"pub:prerelease": "lerna publish --force-publish --cd-version prerelease --npm-tag beta --preid beta --message \"chore(release): publish %v\"",
"pub:prepatch": "lerna publish --force-publish --cd-version prepatch --npm-tag beta --preid beta --message \"chore(release): publish %v\"",
"setup": "./scripts/setup.sh",
"start": "./scripts/start.sh",
"start:server": "./scripts/start-server.sh",

View File

@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* 传递正确的 removeIndex 给到 subtreeModified 钩子 ([822b2fd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/822b2fd))
* 修复 overridePropsConfigure 参数为数组时的逻辑 ([4e58e09](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4e58e09))
* 修复组件不会插入到选中节点之内或者之后的逻辑 ([93b005b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93b005b))
### Features
* 支持 build sourceMap, 方便用户调试 ([6bf75cd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6bf75cd))
* 支持用户修改 builtinComponentActions ([bc183d1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc183d1))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复 prop 无法删除最后一个 item ([e18a386](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e18a386))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -19,6 +19,7 @@ module.exports = {
'!src/**/*.d.ts',
'!src/icons/**',
'!src/locale/**',
'!src/builtin-simulator/utils/**',
'!src/document/node/exclusive-group.ts',
'!**/node_modules/**',
'!**/vendor/**',

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-designer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Designer for Ali LowCode Engine",
"main": "lib/index.js",
"module": "es/index.js",
@ -14,9 +14,9 @@
},
"license": "MIT",
"dependencies": {
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"classnames": "^2.2.6",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
@ -27,6 +27,7 @@
"devDependencies": {
"@ali/lowcode-test-mate": "^1.0.1",
"@alib/build-scripts": "^0.1.29",
"@testing-library/react": "^11.2.2",
"@types/classnames": "^2.2.7",
"@types/jest": "^26.0.16",
"@types/lodash": "^4.14.165",

View File

@ -602,7 +602,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||
node?.componentMeta?.componentName ||
'';
editor?.emit('desiger.builtinSimulator.contextmenu', {
editor?.emit('designer.builtinSimulator.contextmenu', {
selected,
});
});
@ -817,6 +817,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
/**
* @see ISimulator
*/
/* istanbul ignore next */
scrollToNode(node: Node, detail?: any/* , tryTimes = 0 */) {
this.tryScrollAgain = null;
if (this.sensing) {

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

@ -19,8 +19,7 @@ export default class Viewport implements IViewport {
}
get contentBounds(): DOMRect {
const { bounds } = this;
const { scale } = this;
const { bounds, scale } = this;
return new DOMRect(0, 0, bounds.width / scale, bounds.height / scale);
}

View File

@ -178,10 +178,10 @@ export class ComponentMeta {
this._title =
typeof title === 'string'
? {
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
}
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
}
: title;
}
@ -243,14 +243,22 @@ export class ComponentMeta {
}
isRootComponent(includeBlock = true) {
return this.componentName === 'Page' || this.componentName === 'Component' || (includeBlock && this.componentName === 'Block');
return (
this.componentName === 'Page' ||
this.componentName === 'Component' ||
(includeBlock && this.componentName === 'Block')
);
}
@computed get availableActions() {
// eslint-disable-next-line prefer-const
let { disableBehaviors, actions } = this._transformedMetadata?.configure.component || {};
const disabled = ensureAList(disableBehaviors) || (this.isRootComponent(false) ? ['copy', 'remove'] : null);
actions = builtinComponentActions.concat(this.designer.getGlobalComponentActions() || [], actions || []);
const disabled =
ensureAList(disableBehaviors) || (this.isRootComponent(false) ? ['copy', 'remove'] : null);
actions = builtinComponentActions.concat(
this.designer.getGlobalComponentActions() || [],
actions || [],
);
if (disabled) {
if (disabled.includes('*')) {
@ -331,7 +339,11 @@ export interface MetadataTransducer {
}
const metadataTransducers: MetadataTransducer[] = [];
export function registerMetadataTransducer(transducer: MetadataTransducer, level = 100, id?: string) {
export function registerMetadataTransducer(
transducer: MetadataTransducer,
level = 100,
id?: string,
) {
transducer.level = level;
transducer.id = id;
const i = metadataTransducers.findIndex((item) => item.level != null && item.level > level);
@ -360,14 +372,14 @@ registerMetadataTransducer((metadata) => {
childWhitelist: [`${m[1]}`],
};
}
// eslint-disable-next-line no-cond-assign
// eslint-disable-next-line no-cond-assign
} else if ((m = /^(.+)\.Node$/.exec(componentName))) {
// uri match xx.Node set selfControlled: false, parentWhiteList
// component.selfControlled = false;
component.nestingRule = {
parentWhitelist: [`${m[1]}`, componentName],
};
// eslint-disable-next-line no-cond-assign
// eslint-disable-next-line no-cond-assign
} else if ((m = /^(.+)\.(Item|Node|Option)$/.exec(componentName))) {
// uri match .Item .Node .Option set parentWhiteList
component.nestingRule = {
@ -440,3 +452,13 @@ export function removeBuiltinComponentAction(name: string) {
export function addBuiltinComponentAction(action: ComponentAction) {
builtinComponentActions.push(action);
}
export function modifyBuiltinComponentAction(
actionName,
handle: (action: ComponentAction) => void,
) {
const builtinAction = builtinComponentActions.find((action) => action.name === actionName);
if (builtinAction) {
handle(builtinAction);
}
}

View File

@ -31,7 +31,15 @@ export class ActiveTracker {
return this._target?.detail;
}
/**
* @deprecated
*/
/* istanbul ignore next */
get intance() {
return this.instance;
}
get instance() {
return this._target?.instance;
}

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

@ -138,7 +138,7 @@ export class Designer {
parent?.componentMeta?.componentName ||
'';
// eslint-disable-next-line no-unused-expressions
this.editor?.emit('designer.drag', {
this.postEvent('drag', {
time: (endTime - startTime).toFixed(2),
selected: nodes
?.map((n) => {
@ -193,7 +193,7 @@ export class Designer {
this.setupSelection();
setupHistory();
});
this.postEvent('designer.init', this);
this.postEvent('init', this);
this.setupSelection();
setupHistory();

View File

@ -179,7 +179,7 @@ function makeEventsHandler(
}
function isDragEvent(e: any): e is DragEvent {
return e?.type?.substr(0, 4) === 'drag';
return e?.type?.startsWith('drag');
}
/**
@ -245,7 +245,7 @@ export class Dragon {
const handleEvents = makeEventsHandler(boostEvent, masterSensors);
const newBie = !isDragNodeObject(dragObject);
const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlot());
const isBoostFromDragAPI = boostEvent.type.substr(0, 4) === 'drag';
const isBoostFromDragAPI = isDragEvent(boostEvent);
let lastSensor: ISensor | undefined;
this._dragging = false;
@ -259,6 +259,7 @@ export class Dragon {
let copy = false;
const checkcopy = (e: MouseEvent | DragEvent | KeyboardEvent) => {
/* istanbul ignore next */
if (isDragEvent(e) && e.dataTransfer) {
if (newBie || forceCopyState) {
e.dataTransfer.dropEffect = 'copy';
@ -272,6 +273,7 @@ export class Dragon {
if (e.altKey || e.ctrlKey) {
copy = true;
this.setCopyState(true);
/* istanbul ignore next */
if (isDragEvent(e) && e.dataTransfer) {
e.dataTransfer.dropEffect = 'copy';
}
@ -279,6 +281,7 @@ export class Dragon {
copy = false;
if (!forceCopyState) {
this.setCopyState(false);
/* istanbul ignore next */
if (isDragEvent(e) && e.dataTransfer) {
e.dataTransfer.dropEffect = 'move';
}
@ -332,6 +335,7 @@ export class Dragon {
// route: drag-move
const move = (e: MouseEvent | DragEvent) => {
/* istanbul ignore next */
if (isBoostFromDragAPI) {
e.preventDefault();
}
@ -350,6 +354,7 @@ export class Dragon {
};
let didDrop = true;
/* istanbul ignore next */
const drop = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
@ -358,12 +363,14 @@ export class Dragon {
// end-tail drag process
const over = (e?: any) => {
/* istanbul ignore next */
if (e && isDragEvent(e)) {
e.preventDefault();
}
if (lastSensor) {
lastSensor.deactiveSensor();
}
/* istanbul ignore next */
if (isBoostFromDragAPI) {
if (!didDrop) {
designer.clearLocation();
@ -385,6 +392,7 @@ export class Dragon {
designer.clearLocation();
handleEvents((doc) => {
/* istanbul ignore next */
if (isBoostFromDragAPI) {
doc.removeEventListener('dragover', move, true);
doc.removeEventListener('dragend', over, true);
@ -418,7 +426,7 @@ export class Dragon {
if (!sourceDocument || sourceDocument === document) {
evt.globalX = e.clientX;
evt.globalY = e.clientY;
} else {
} /* istanbul ignore next */ else {
// event from simulator sandbox
let srcSim: ISimulatorHost | undefined;
const lastSim = lastSensor && isSimulatorHost(lastSensor) ? lastSensor : null;
@ -449,6 +457,7 @@ export class Dragon {
};
const sourceSensor = getSourceSensor(dragObject);
/* istanbul ignore next */
const chooseSensor = (e: LocateEvent) => {
// this.sensors will change on dragstart
const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors);
@ -477,6 +486,7 @@ export class Dragon {
return sensor;
};
/* istanbul ignore next */
if (isDragEvent(boostEvent)) {
const { dataTransfer } = boostEvent;
@ -496,6 +506,7 @@ export class Dragon {
}
handleEvents((doc) => {
/* istanbul ignore next */
if (isBoostFromDragAPI) {
doc.addEventListener('dragover', move, true);
// dragexit

View File

@ -18,18 +18,18 @@ export class ScrollTarget {
}
get scrollHeight(): number {
return ((this.doe || this.target) as any).scrollHeight;
return ((this.doc || this.target) as any).scrollHeight;
}
get scrollWidth(): number {
return ((this.doe || this.target) as any).scrollWidth;
return ((this.doc || this.target) as any).scrollWidth;
}
private doe?: HTMLElement;
private doc?: HTMLElement;
constructor(private target: Window | Element) {
if (isWindow(target)) {
this.doe = target.document.documentElement;
this.doc = target.document.documentElement;
}
}
}

View File

@ -86,7 +86,7 @@ export class SettingPropEntry implements SettingEntry {
}
const propName = this.path.join('.');
let l = this.nodes.length;
while (l-- > 1) {
while (l-- > 0) {
this.nodes[l].getProp(propName, true)!.key = key;
}
this._name = key;

View File

@ -128,6 +128,8 @@ export class NodeChildren {
slotNode.remove(useMutator, purge);
}, (iterable, idx) => (iterable as [])[idx]);
}
// 需要在从 children 中删除 node 前记录下 indexinternalSetParent 中会执行删除(unlink)操作
const i = this.children.indexOf(node);
if (purge) {
// should set parent null
node.internalSetParent(null, useMutator);
@ -142,14 +144,13 @@ export class NodeChildren {
document.selection.remove(node.id);
document.destroyNode(node);
this.emitter.emit('change');
const i = this.children.indexOf(node);
if (useMutator) {
this.reportModified(node, this.owner, { type: 'remove', removeIndex: i, removeNode: node });
}
if (i < 0) {
return false;
// purge 为 true 时,已在 internalSetParent 中删除了子节点
if (i > -1 && !purge) {
this.children.splice(i, 1);
}
this.children.splice(i, 1);
return false;
}

View File

@ -937,7 +937,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
}
if (this.parent) {
return this.parent.getSuitablePlace(node, ref);
return this.parent.getSuitablePlace(node, { index: this.index });
}
return null;

View File

@ -15,10 +15,14 @@ export function getConvertedExtraKey(key: string): string {
if (key.indexOf('.') > 0) {
_key = key.split('.')[0];
}
return EXTRA_KEY_PREFIX + _key + EXTRA_KEY_PREFIX + key.substr(_key.length);
return EXTRA_KEY_PREFIX + _key + EXTRA_KEY_PREFIX + key.substr(_key.length + 1);
}
export function getOriginalExtraKey(key: string): string {
return key.replace(new RegExp(`${EXTRA_KEY_PREFIX}`, 'g'), '');
// 移除串首、串尾的 EXTRA_KEY_PREFIX将剩下的转成 .
return key
.replace(new RegExp(`^${EXTRA_KEY_PREFIX}`), '')
.replace(new RegExp(`${EXTRA_KEY_PREFIX}$`), '')
.replace(new RegExp(`${EXTRA_KEY_PREFIX}`, 'g'), '.');
}
export class Props implements IPropParent {

View File

@ -0,0 +1,131 @@
import '../../fixtures/window';
import { 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 { Designer } from '../../../src/designer/designer';
import DragResizeEngine from '../../../src/builtin-simulator/bem-tools/drag-resize-engine';
import formSchema from '../../fixtures/schema/form';
import divMetadata from '../../fixtures/component-metadata/div';
import formMetadata from '../../fixtures/component-metadata/form';
import otherMeta from '../../fixtures/component-metadata/other';
import pageMetadata from '../../fixtures/component-metadata/page';
import { fireEvent, createEvent } from '@testing-library/react';
import { create } from 'lodash';
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 });
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 mockedBoostFn = jest
.fn((e) => {
return boostedNode;
});
// do nothing
resizeEngine.from();
const offFrom = resizeEngine.from(document, 'e', mockedBoostFn);
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 mockedDoc = document.createElement('iframe').contentWindow?.document;
project.mountSimulator({
sensorAvailable: true,
contentDocument: document,
});
const mockedBoostFn = jest
.fn((e) => {
return doc.getNode('node_k1ow3cbn');
});
const offFrom = resizeEngine.from(document, 'e', mockedBoostFn);
// TODO: 想办法 mock 一个 iframe.currentDocument
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
});
});

View File

@ -0,0 +1,487 @@
import React from 'react';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../fixtures/window';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import {
AssetLevel,
Asset,
AssetList,
assetBundle,
assetItem,
AssetType,
} from '@ali/lowcode-utils';
import {
Dragon,
isDragNodeObject,
isDragNodeDataObject,
isDragAnyObject,
isLocateEvent,
DragObjectType,
isShaken,
setShaken,
} from '../../src/designer/dragon';
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, delayObxTick } from '../utils';
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
import { fireEvent } from '@testing-library/react';
describe('Host 测试', () => {
let editor: Editor;
let designer: Designer;
let project: Project;
let doc: DocumentModel;
let host: BuiltinSimulatorHost;
beforeAll(() => {
editor = new Editor();
!globalContext.has(Editor) && globalContext.register(editor, Editor);
});
beforeEach(() => {
designer = new Designer({ editor });
project = designer.project;
doc = project.createDocument(formSchema);
host = new BuiltinSimulatorHost(designer.project);
});
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).toBe(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: DragObjectType.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: DragObjectType.Node,
nodes: [],
},
})).toBeUndefined();
});
it('locate没有 document', () => {
project.removeDocument(doc);
expect(host.locate({
dragObject: {
type: DragObjectType.Node,
nodes: [doc.getNode('page')],
},
})).toBeNull();
});
it('locate', () => {
host.locate({
dragObject: {
type: DragObjectType.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' }},
};
}
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,145 +0,0 @@
import React from 'react';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../fixtures/window';
import { Editor } from '@ali/lowcode-editor-core';
import {
AssetLevel,
Asset,
AssetList,
assetBundle,
assetItem,
AssetType,
} from '@ali/lowcode-utils';
import { Project } from '../../src/project/project';
import { Node } from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer';
import formSchema from '../fixtures/schema/form';
import { getMockDocument, getMockWindow, getMockEvent } from '../utils';
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
import { eq } from 'lodash';
const editor = new Editor();
describe('host 测试', () => {
let designer: Designer;
beforeEach(() => {
designer = new Designer({ editor });
});
afterEach(() => {
designer._componentMetasMap.clear();
designer = null;
});
it('基础方法测试', async () => {
const host = new BuiltinSimulatorHost(designer.project);
expect(host.currentDocument).toBe(designer.project.currentDocument);
expect(host.renderEnv).toBe('default');
expect(host.device).toBe('default');
expect(host.deviceClassName).toBeUndefined();
host.setProps({
renderEnv: 'rax',
device: 'mobile',
deviceClassName: 'mobile-rocks',
componentsAsset: [{
type: AssetType.JSText,
content: 'console.log(1)',
}, {
type: AssetType.JSUrl,
content: '//path/to/js',
}],
theme: {
type: AssetType.CSSText,
content: '.theme {font-size: 50px;}',
}
});
expect(host.renderEnv).toBe('rax');
expect(host.device).toBe('mobile');
expect(host.deviceClassName).toBe('mobile-rocks');
expect(host.componentsAsset).toEqual([{
type: AssetType.JSText,
content: 'console.log(1)',
}, {
type: AssetType.JSUrl,
content: '//path/to/js',
}]);
expect(host.theme).toEqual({
type: AssetType.CSSText,
content: '.theme {font-size: 50px;}',
});
expect(host.componentsMap).toBe(designer.componentsMap);
host.set('renderEnv', 'vue');
expect(host.renderEnv).toBe('vue');
expect(host.getComponentContext).toThrow('Method not implemented.');
});
it('事件测试', async () => {
const host = new BuiltinSimulatorHost(designer.project);
const mockDocument = getMockDocument();
const mockWindow = getMockWindow(mockDocument);
const mockIframe = {
contentWindow: mockWindow,
contentDocument: mockDocument,
dispatchEvent() {},
};
// 非法分支测试
host.mountContentFrame();
expect(host._iframe).toBeUndefined();
host.set('library', [{
package: '@ali/vc-deep',
library: 'lib',
urls: ['a.js', 'b.js']
}]);
host.componentsConsumer.consume(() => {});
host.injectionConsumer.consume(() => {});
await host.mountContentFrame(mockIframe);
expect(host.contentWindow).toBe(mockWindow);
mockDocument.triggerEventListener(
'mouseover',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'mouseleave',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'mousedown',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'mouseup',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'mousemove',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'click',
getMockEvent(document.createElement('input')),
host,
);
mockDocument.triggerEventListener(
'dblclick',
getMockEvent(mockDocument.createElement('div')),
host,
);
mockDocument.triggerEventListener(
'contextmenu',
getMockEvent(mockDocument.createElement('div')),
host,
);
})
});

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

@ -6,6 +6,7 @@ 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';
const editor = new Editor();
@ -28,8 +29,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 +38,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 +47,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 +56,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 +66,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 +76,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 +86,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 +94,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 +107,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 +124,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 +132,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 +143,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 +167,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 +180,109 @@ 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
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

@ -0,0 +1,346 @@
import '../fixtures/window';
import { 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 {
isRootNode,
Node,
isNode,
comparePosition,
contains,
insertChild,
insertChildren,
PositionNO,
} from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer';
import {
Dragon,
isDragNodeObject,
isDragNodeDataObject,
isDragAnyObject,
isLocateEvent,
DragObjectType,
isShaken,
setShaken,
} from '../../src/designer/dragon';
import formSchema from '../fixtures/schema/form';
import divMetadata from '../fixtures/component-metadata/div';
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';
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 });
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: DragObjectType.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: DragObjectType.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: DragObjectType.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: DragObjectType.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: DragObjectType.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: DragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
},
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
);
const mockedFn1 = jest.fn();
project.mountSimulator({ setCopyState: mockedFn1 });
expect(dragon.getSimulators().size).toBe(1);
fireEvent.keyDown(document, { ctrlKey: true });
expect(mockedFn1).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 mockedBoostFn = jest
.fn((e) => {
return {
type: DragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
};
})
.mockImplementationOnce(() => null);
const offFrom = dragon.from(document, mockedBoostFn);
// 无用 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 = {};
dragon.addSensor(sensor);
expect(dragon.sensors.length).toBe(1);
dragon.removeSensor(sensor);
expect(dragon.sensors.length).toBe(0);
});
it('has sensor', () => {
const mockedFn1 = jest.fn();
const mockedDoc = document.createElement('iframe').contentWindow?.document;
dragon.addSensor({
fixEvent: () => {},
locate: () => {},
contentDocument: mockedDoc,
});
project.mountSimulator({
setCopyState: mockedFn1,
setNativeSelection: () => {},
clearState: () => {},
setDraggingState: () => {},
});
const mockedBoostFn = jest
.fn((e) => {
return {
type: DragObjectType.Node,
nodes: [doc.getNode('node_k1ow3cbn')],
};
})
.mockImplementationOnce(() => null);
const offFrom = dragon.from(document, mockedBoostFn);
// TODO: 想办法 mock 一个 iframe.currentDocument
fireEvent.mouseDown(document, { clientX: 100, clientY: 100 });
});
});
describe('导出的其他函数', () => {
it('isDragNodeObject', () => {
expect(isDragNodeObject({ type: DragObjectType.Node, nodes: [] })).toBeTruthy();
});
it('isDragNodeDataObject', () => {
expect(isDragNodeDataObject({ type: DragObjectType.NodeData, data: [] })).toBeTruthy();
});
it('isDragAnyObject', () => {
expect(isDragAnyObject()).toBeFalsy();
expect(isDragAnyObject({ type: DragObjectType.Node, nodes: [] })).toBeFalsy();
expect(isDragAnyObject({ type: DragObjectType.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();
});
});

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

@ -0,0 +1,158 @@
import '../fixtures/window';
import { 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 { ScrollTarget, Scroller } from '../../src/designer/scroller';
import {
isRootNode,
isNode,
comparePosition,
contains,
insertChild,
insertChildren,
PositionNO,
} from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer';
import {
Dragon,
isDragNodeObject,
isDragNodeDataObject,
isDragAnyObject,
isLocateEvent,
DragObjectType,
isShaken,
setShaken,
} from '../../src/designer/dragon';
import formSchema from '../fixtures/schema/form';
import divMetadata from '../fixtures/component-metadata/div';
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';
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 });
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

@ -93,7 +93,7 @@ describe('setting-top-entry 测试', () => {
const { currentDocument } = designer.project;
const divNode = currentDocument?.getNode('div');
console.log(divNode?.getPropValue('behavior'));
// console.log(divNode?.getPropValue('behavior'));
const { settingEntry } = divNode!;
expect(typeof settingEntry.getChildren).toBe('function');

View File

@ -160,10 +160,10 @@ describe('Node 方法测试', () => {
const pageMeta = designer.getComponentMeta('Page');
set(pageMeta, 'prototype.options.canDropIn', () => true);
const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), 1);
const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), { index: 1 });
expect(o).toEqual({
container: doc.rootNode,
ref: 1,
ref: { index: 1 },
});
});

View File

@ -0,0 +1,394 @@
import '../../../fixtures/window';
import { set } from '../../../utils';
import { Editor } from '@ali/lowcode-editor-core';
import { Props } 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 { Prop, isProp, isValidArrayIndex } from '../../../../src/document/node/props/prop';
import { TransformStage } from '@ali/lowcode-types';
import { delayObxTick } from '../../../utils';
const mockedOwner = {
componentName: 'Div',
};
const mockedPropsInst = {
owner: mockedOwner,
};
mockedPropsInst.props = mockedPropsInst;
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(mockedPropsInst, true, 'boolProp');
strProp = new Prop(mockedPropsInst, 'haha', 'strProp');
numProp = new Prop(mockedPropsInst, 1, 'numProp');
nullProp = new Prop(mockedPropsInst, null, 'nullProp');
expProp = new Prop(mockedPropsInst, { type: 'JSExpression', value: 'state.haha' }, 'expProp');
// slotProp = new Prop(mockedPropsInst, { type: 'JSSlot', value: [{ componentName: 'Button' }] }, 'slotProp');
});
afterEach(() => {
boolProp.purge();
strProp.purge();
numProp.purge();
nullProp.purge();
expProp.purge();
// slotProp.purge();
});
it('consturctor / getProps / getNode', () => {
expect(boolProp.parent).toBe(mockedPropsInst);
expect(boolProp.getProps()).toBe(mockedPropsInst);
expect(boolProp.getNode()).toBe(mockedOwner);
});
it('misc', () => {
expect(boolProp.get('x', false)).toBeNull();
expect(boolProp.maps).toBeNull();
expect(boolProp.add()).toBeNull();
strProp.unset();
strProp.add(2, true);
strProp.set(1);
expect(numProp.set()).toBeNull();
expect(numProp.has()).toBeFalsy();
});
it('getValue / getAsString / setValue', () => {
expect(strProp.getValue()).toBe('haha');
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(TransformStage.Save)).toBe(true);
expect(strProp.export(TransformStage.Save)).toBe('haha');
expect(numProp.export(TransformStage.Save)).toBe(1);
expect(nullProp.export(TransformStage.Save)).toBe('');
expect(nullProp.export(TransformStage.Serilize)).toBe(null);
expect(expProp.export(TransformStage.Save)).toEqual({
type: 'JSExpression',
value: 'state.haha',
});
strProp.unset();
expect(strProp.getValue()).toBeUndefined();
expect(strProp.isUnset()).toBeTruthy();
expect(strProp.export(TransformStage.Save)).toBeUndefined();
expect(
new Prop(mockedPropsInst, false, '___condition___').export(TransformStage.Render),
).toBeTruthy();
// console.log(slotProp.export(TransformStage.Render));
});
it('compare', () => {
const newProp = new Prop(mockedPropsInst, 'haha');
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);
});
it('isVirtual', () => {
expect(new Prop(mockedPropsInst, 111, '!virtualProp')).toBeTruthy();
});
it('purge', () => {
boolProp.purge();
expect(boolProp.purged).toBeTruthy();
boolProp.purge();
});
it('迭代器 / map / forEach', () => {
const mockedFn = jest.fn();
for (let item of strProp) {
mockedFn();
}
expect(mockedFn).not.toHaveBeenCalled();
mockedFn.mockClear();
strProp.forEach(item => {
mockedFn();
});
expect(mockedFn).not.toHaveBeenCalled();
mockedFn.mockClear();
strProp.map(item => {
mockedFn();
});
expect(mockedFn).not.toHaveBeenCalled();
mockedFn.mockClear();
});
});
describe('复杂类型', () => {
describe('items(map 类型)', () => {
let prop: Prop;
beforeEach(() => {
prop = new Prop(mockedPropsInst, {
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
});
});
afterEach(() => {
prop.purge();
});
it('items / get', async () => {
expect(prop.size).toBe(5);
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 fromStashProp = prop.get('l');
const fromStashNestedProp = prop.get('m.m1');
fromStashProp.setValue('fromStashProp');
fromStashNestedProp?.setValue('fromStashNestedProp')
await delayObxTick();
expect(prop.get('l').getValue()).toBe('fromStashProp');
expect(prop.get('m.m1').getValue()).toBe('fromStashNestedProp');
});
it('export', () => {
// TODO: 需要访问一下才能触发构造 _items
prop.items;
expect(prop.export()).toEqual({
a: 1,
b: 'str',
c: true,
d: {
type: 'JSExpression',
value: 'state.a',
},
z: {
z1: 1,
z2: 'str',
},
});
});
it('compare', () => {
const prop1 = new Prop(mockedPropsInst, { a: 1 });
const prop2 = new Prop(mockedPropsInst, { 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 mockedFn = jest.fn();
for (let item of prop) {
mockedFn();
}
expect(mockedFn).toHaveBeenCalledTimes(5);
mockedFn.mockClear();
prop.forEach(item => {
mockedFn();
});
expect(mockedFn).toHaveBeenCalledTimes(5);
mockedFn.mockClear();
prop.map(item => {
mockedFn();
});
expect(mockedFn).toHaveBeenCalledTimes(5);
mockedFn.mockClear();
});
it('dispose', () => {
prop.dispose();
expect(prop._items).toBeNull();
expect(prop._maps).toBeNull();
});
});
describe('items(list 类型)', () => {
let prop: Prop;
beforeEach(() => {
prop = new Prop(mockedPropsInst, [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('');
});
it('export', () => {
// 触发构造
prop.items;
expect(prop.export()).toEqual([1, true, 'haha']);
});
it('compare', () => {
const prop1 = new Prop(mockedPropsInst, [1]);
const prop2 = new Prop(mockedPropsInst, [2]);
const prop3 = new Prop(mockedPropsInst, [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();
});
});
});
describe('slotNode / setAsSlot', () => {
const editor = new Editor();
const designer = new Designer({ editor });
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(TransformStage.Serilize).value[0].componentName).toBe('Button');
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();
});
});

View File

@ -0,0 +1,244 @@
import '../../../fixtures/window';
import { set } from '../../../utils';
import { Editor } from '@ali/lowcode-editor-core';
import { Props, getConvertedExtraKey, getOriginalExtraKey } 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 { Prop, isProp, isValidArrayIndex } from '../../../../src/document/node/props/props';
import { TransformStage } from '@ali/lowcode-types';
import { delayObxTick } from '../../../utils';
const mockedOwner = { componentName: 'Page' };
describe('Props 类测试', () => {
let props: Props;
beforeEach(() => {
props = new Props(mockedOwner, {
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(mockedOwner);
});
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 fromStashProp = props.get('l', true);
const fromStashNestedProp = props.get('m.m1', true);
fromStashProp.setValue('fromStashProp');
fromStashNestedProp?.setValue('fromStashNestedProp')
await delayObxTick();
expect(props.get('l').getValue()).toBe('fromStashProp');
expect(props.get('m.m1').getValue()).toBe('fromStashNestedProp');
});
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('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 mockedFn = jest.fn();
for (let item of props) {
mockedFn();
}
expect(mockedFn).toHaveBeenCalledTimes(6);
mockedFn.mockClear();
props.forEach(item => {
mockedFn();
});
expect(mockedFn).toHaveBeenCalledTimes(6);
mockedFn.mockClear();
props.map(item => {
mockedFn();
});
expect(mockedFn).toHaveBeenCalledTimes(6);
mockedFn.mockClear();
props.filter(item => {
mockedFn();
});
expect(mockedFn).toHaveBeenCalledTimes(6);
mockedFn.mockClear();
});
it('purge', () => {
props.purge();
expect(props.purged).toBeTruthy();
});
it('empty items', () => {
expect(new Props(mockedOwner).export()).toEqual({});
});
describe('list 类型', () => {
let props: Props;
beforeEach(() => {
props = new Props(mockedOwner, [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');
});
it('getOriginalExtraKey', () => {
expect(getOriginalExtraKey('___a___')).toBe('a');
expect(getOriginalExtraKey('___a___b')).toBe('a.b');
});
});

View File

@ -221,10 +221,9 @@ export default {
},
],
component: {
isContainer: true,
nestingRule: {
parentWhitelist: 'Div',
childWhitelist: 'Div',
// parentWhitelist: 'Div',
// childWhitelist: 'Div',
},
},
supports: {},

View File

@ -223,8 +223,8 @@ export default {
component: {
isContainer: true,
nestingRule: {
parentWhitelist: 'Div',
childWhitelist: 'Div',
// parentWhitelist: 'Div',
// childWhitelist: 'Div',
},
},
supports: {},

View File

@ -15,4 +15,6 @@ Object.defineProperty(window, 'matchMedia', {
Object.defineProperty(window, 'React', {
writable: true,
value: {},
});
});
window.scrollTo = () => {};

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,
}
})
@ -74,4 +76,36 @@ export function getMockWindow(doc?: MockDocument) {
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

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

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-editor-core
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-editor-core
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-editor-core",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Core Api for Ali lowCode engine",
"license": "MIT",
"main": "lib/index.js",
@ -15,8 +15,8 @@
"cloud-build": "build-scripts build --skip-demo"
},
"dependencies": {
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@alifd/next": "^1.19.16",
"@recore/obx": "^1.0.9",
"@recore/obx-react": "^1.0.8",

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-editor-preset-general
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-editor-preset-general
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-editor-preset-general",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Ali General Editor Preset",
"main": "lib/index.js",
"files": [
@ -14,12 +14,12 @@
},
"license": "MIT",
"dependencies": {
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-editor-skeleton": "^1.0.24-beta.4",
"@ali/lowcode-plugin-designer": "^1.0.24-beta.4",
"@ali/lowcode-plugin-outline-pane": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"@ali/lowcode-editor-skeleton": "^1.0.26-beta.0",
"@ali/lowcode-plugin-designer": "^1.0.26-beta.0",
"@ali/lowcode-plugin-outline-pane": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@alifd/next": "^1.19.12",
"@alife/theme-lowcode-dark": "^0.1.0",
"@alife/theme-lowcode-light": "^0.1.0",

View File

@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* 修复 overridePropsConfigure 参数为数组时的逻辑 ([4e58e09](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4e58e09))
### Features
* 支持 build sourceMap, 方便用户调试 ([6bf75cd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6bf75cd))
* 支持用户修改 builtinComponentActions ([bc183d1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc183d1))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复大纲树和组件面板来回点击异常 ([8b9a6ec](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b9a6ec))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,4 +1,5 @@
{
"sourceMap": true,
"plugins": [
[
"build-plugin-component",

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-editor-preset-vision",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Vision Polyfill for Ali lowCode engine",
"main": "lib/index.js",
"private": true,
@ -17,13 +17,13 @@
},
"license": "MIT",
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"@ali/lowcode-editor-setters": "^1.0.22",
"@ali/lowcode-editor-skeleton": "^1.0.24-beta.4",
"@ali/lowcode-plugin-designer": "^1.0.24-beta.4",
"@ali/lowcode-plugin-outline-pane": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-editor-skeleton": "^1.0.26-beta.0",
"@ali/lowcode-plugin-designer": "^1.0.26-beta.0",
"@ali/lowcode-plugin-outline-pane": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@ali/ve-i18n-util": "^2.0.0",
"@ali/ve-icons": "^4.1.9",
"@ali/ve-less-variables": "2.0.3",

View File

@ -127,7 +127,11 @@ registerMetadataTransducer(
const override = Overrides[componentName]?.override;
if (override) {
if (Array.isArray(override)) {
metadata.configure.combined = override;
// 替换 #props其他暂时忽略
const idx = metadata.configure.combined?.findIndex(item => item.name === '#props');
if (idx > -1) {
metadata.configure.combined[idx].items = override;
}
} else {
let l = top.length;
let item;

View File

@ -5,7 +5,12 @@ import logger from '@ali/vu-logger';
import { render } from 'react-dom';
import I18nUtil from './i18n-util';
import { hotkey as Hotkey, monitor } from '@ali/lowcode-editor-core';
import { registerMetadataTransducer } from '@ali/lowcode-designer';
import {
registerMetadataTransducer,
addBuiltinComponentAction,
removeBuiltinComponentAction,
modifyBuiltinComponentAction,
} from '@ali/lowcode-designer';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
import Bus from './bus';
@ -68,8 +73,16 @@ const modules = {
Prop,
};
const designerHelper = {
registerMetadataTransducer,
addBuiltinComponentAction,
removeBuiltinComponentAction,
// modifyBuiltinComponentAction,
};
const VisualEngine = {
designer,
designerHelper,
editor,
skeleton,
/**
@ -121,6 +134,7 @@ export default VisualEngine;
export {
designer,
designerHelper,
editor,
skeleton,
/**

View File

@ -206,9 +206,9 @@ const dockPane = Object.assign(skeleton.leftArea, {
const f = (_: any, dock: any) => {
fn(dock);
};
editor.on('skeleton.panel-dock.show', f);
editor.on('skeleton.panel-dock.active', f);
return () => {
editor.removeListener('skeleton.panel-dock.show', f);
editor.removeListener('skeleton.panel-dock.active', f);
};
},
/**
@ -218,9 +218,9 @@ const dockPane = Object.assign(skeleton.leftArea, {
const f = (_: any, dock: any) => {
fn(dock);
};
editor.on('skeleton.panel-dock.hide', f);
editor.on('skeleton.panel-dock.unactive', f);
return () => {
editor.removeListener('skeleton.panel-dock.hide', f);
editor.removeListener('skeleton.panel-dock.unactive', f);
};
},
/**

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-editor-skeleton
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复大纲树和组件面板来回点击异常 ([8b9a6ec](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b9a6ec))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-editor-skeleton",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "alibaba lowcode editor skeleton",
"main": "lib/index.js",
"module": "es/index.js",
@ -19,10 +19,10 @@
"editor"
],
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@ali/ve-icons": "latest",
"@ali/ve-less-variables": "^2.0.0",
"@alifd/next": "^1.20.12",

View File

@ -162,6 +162,11 @@ export default class Panel implements IWidget {
if (flag) {
this._actived = true;
this.parent?.active(this);
if (this.parent.name === 'leftFloatArea') {
this.skeleton.leftFixedArea.container.unactiveAll();
} else if (this.parent.name === 'leftFixedArea') {
this.skeleton.leftFloatArea.container.unactiveAll();
}
if (!this.inited) {
this.inited = true;
}

View File

@ -79,6 +79,10 @@ export default class WidgetContainer<T extends WidgetItem = any, G extends Widge
}
}
unactiveAll() {
Object.keys(this.maps).forEach(name => this.unactive(name));
}
add(item: T | G): T {
item = this.handle(item);
const origin = this.get(item.name);

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-ignitor
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-ignitor
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-ignitor",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "点火器bootstrap lce project",
"main": "lib/index.js",
"private": true,

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-plugin-designer
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-plugin-designer
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-plugin-designer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "alibaba lowcode editor designer plugin",
"files": [
"es",
@ -20,8 +20,8 @@
],
"author": "xiayang.xy",
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-plugin-outline-pane",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Outline pane for Ali lowCode engine",
"files": [
"es",
@ -14,10 +14,10 @@
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-editor-core": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-editor-core": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"react": "^16",

View File

@ -3,7 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.24-beta.4"></a>
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* rax perf ([3abe2ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3abe2ab))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-rax-renderer
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)
@ -11,7 +30,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package @ali/lowcode-rax-renderer
<a name="1.0.24-beta.3"></a>
<a name="1.0.24-beta.3"></a>
## [1.0.24-beta.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.2...v1.0.24-beta.3) (2020-12-11)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-rax-renderer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Rax renderer for Ali lowCode engine",
"main": "lib/index.js",
"module": "lib/index.js",
@ -37,7 +37,7 @@
"@ali/bzb-request": "2.6.1",
"@ali/lib-mtop": "^2.5.1",
"@ali/lowcode-datasource-engine": "^1.0.22",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@ali/ui-table": "^1.0.1-beta.6",
"classnames": "^2.2.6",
"debug": "^4.1.1",

View File

@ -0,0 +1,11 @@
import { createElement, PureComponent } from 'rax';
export default class DivView extends PureComponent {
static displayName = 'Div';
static version = '0.0.0';
render(): any {
return createElement('div', this.props);
}
}

View File

@ -23,6 +23,7 @@ import {
getFileCssName,
} from '../utils';
import VisualDom from '../comp/visualDom';
import Div from '../comp/Div';
import AppContext from '../context/appContext';
import compWrapper from '../hoc/compWrapper';
@ -248,185 +249,214 @@ export default class BaseEngine extends Component {
// parentInfo 父组件的信息包含schema和Comp
// idx 若为循环渲染的循环Index
__createVirtualDom = (schema, self, parentInfo, idx) => {
if (!schema) return null;
// rax text prop 兼容处理
if (schema.componentName === 'Text') {
if (typeof schema.props.text === 'string') {
schema = { ...schema };
schema.children = [schema.props.text];
}
}
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } = this.props || {};
const { engine } = this.context || {};
if (isJSExpression(schema)) {
return parseExpression(schema, self);
}
if (typeof schema === 'string') return schema;
if (typeof schema === 'number' || typeof schema === 'boolean') {
return schema.toString();
}
if (Array.isArray(schema)) {
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
return schema.map((item, idx) => this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx));
}
try {
if (!schema) return null;
if (schema.componentName === 'Text') { // 这个是不是不应该在这里处理
if (typeof schema.props.text === 'string') {
schema = { ...schema };
schema.children = [schema.props.text];
}
}
// 解析占位组件
if (schema.componentName === 'Flagment' && schema.children) {
const tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
return this.__createVirtualDom(tarChildren, self, parentInfo);
}
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
if (schema.$$typeof) {
return schema;
}
if (!isSchema(schema)) return null;
let Comp = components[schema.componentName] || engine.getNotFoundComponent();
if (isJSExpression(schema)) {
return parseExpression(schema, self);
}
if (isJSSlot(schema)) {
return this.__createVirtualDom(schema.value, self, parentInfo);
}
if (typeof schema === 'string') return schema;
if (typeof schema === 'number' || typeof schema === 'boolean') {
return schema.toString();
}
if (Array.isArray(schema)) {
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
return schema.map((item, idy) => this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idy));
}
// FIXME
const _children = this.getSchemaChildren(schema);
// 解析占位组件
if (schema.componentName === 'Flagment' && _children) {
const tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children;
return this.__createVirtualDom(tarChildren, self, parentInfo);
}
if (schema.hidden) {
return null;
}
if (schema.$$typeof) {
return schema;
}
if (!isSchema(schema)) return null;
let Comp = components[schema.componentName] || engine.getNotFoundComponent();
if (schema.loop !== undefined) {
return this.__createLoopVirtualDom(
{
...schema,
loop: parseData(schema.loop, self),
},
if (schema.hidden) {
return null;
}
if (schema.loop != null) {
const loop = parseData(schema.loop, self);
if ((Array.isArray(loop) && loop.length > 0) || isJSExpression(loop)) {
return this.__createLoopVirtualDom(
{
...schema,
loop,
},
self,
parentInfo,
idx,
);
}
}
const condition = schema.condition == null ? true : parseData(schema.condition, self);
if (!condition) return null;
let scopeKey = '';
// 判断组件是否需要生成scope且只生成一次挂在this.__compScopes上
if (Comp.generateScope) {
const key = parseExpression(schema.props.key, self);
if (key) {
// 如果组件自己设置key则使用组件自己的key
scopeKey = key;
} else if (!schema.__ctx) {
// 在生产环境schema没有__ctx上下文需要手动生成一个lunaKey
schema.__ctx = {
lunaKey: `luna${++scopeIdx}`,
};
scopeKey = schema.__ctx.lunaKey;
} else {
// 需要判断循环的情况
scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : '');
}
if (!this.__compScopes[scopeKey]) {
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
}
}
// 如果组件有设置scope需要为组件生成一个新的scope上下文
if (scopeKey && this.__compScopes[scopeKey]) {
const compSelf = { ...this.__compScopes[scopeKey] };
compSelf.__proto__ = self;
self = compSelf;
}
// 容器类组件的上下文通过props传递避免context传递带来的嵌套问题
const otherProps = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
}
const componentInfo = {};
const props =
this.__parseProps(schema.props, self, '', {
schema,
Comp,
componentInfo: {
...componentInfo,
props: transformArrayToMap(componentInfo.props, 'name'),
},
}) || {};
// 对于可以获取到ref的组件做特殊处理
if (!acceptsRef(Comp)) {
Comp = compWrapper(Comp);
}
// if (acceptsRef(Comp)) {
otherProps.ref = (ref) => {
this.$(props.fieldId, ref); // 收集ref
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
ref && engine && engine.props.onCompGetRef(schema, ref);
};
// }
// scope需要传入到组件上
if (scopeKey && this.__compScopes[scopeKey]) {
props.__scope = this.__compScopes[scopeKey];
}
// FIXME 这里清除 key 是为了避免循环渲染中更改 key 导致的渲染重复
props.key = '';
if (schema.__ctx && schema.__ctx.lunaKey) {
if (!isFileSchema(schema)) {
engine && engine.props.onCompGetCtx(schema, self);
}
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
props.__id = schema.id;
if (!props.key) {
props.key = props.__id;
}
let child = null;
if (/*!isFileSchema(schema) && */!!_children) {
child = this.__createVirtualDom(
isJSExpression(_children) ? parseExpression(_children, self) : _children,
self,
{
schema,
Comp,
},
);
}
const renderComp = (props) => engine.createElement(Comp, props, child);
// 设计模式下的特殊处理
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
// 对于overlay,dialog等组件为了使其在设计模式下显示外层需要增加一个div容器
if (OVERLAY_LIST.includes(schema.componentName)) {
const { ref, ...overlayProps } = otherProps;
return (
<Div ref={ref} __designMode={engine.props.designMode}>
{renderComp({ ...props, ...overlayProps })}
</Div>
);
}
// 虚拟dom显示
if (componentInfo && componentInfo.parentRule) {
const parentList = componentInfo.parentRule.split(',');
const { schema: parentSchema, Comp: parentComp } = parentInfo;
if (
!parentList.includes(parentSchema.componentName) ||
parentComp !== components[parentSchema.componentName]
) {
props.__componentName = schema.componentName;
Comp = VisualDom;
} else {
// 若虚拟dom在正常的渲染上下文中就不显示设计模式了
props.__disableDesignMode = true;
}
}
}
return renderComp({ ...props, ...otherProps });
} catch (e) {
return engine.createElement(engine.getFaultComponent(), {
error: e,
schema,
self,
parentInfo,
idx,
);
});
}
const condition = schema.condition === undefined ? true : parseData(schema.condition, self);
if (!condition) return null;
};
let scopeKey = '';
// 判断组件是否需要生成scope且只生成一次挂在this.__compScopes上
if (Comp.generateScope) {
const key = parseExpression(schema.props.key, self);
if (key) {
// 如果组件自己设置key则使用组件自己的key
scopeKey = key;
} else if (!schema.__ctx) {
// 在生产环境schema没有__ctx上下文需要手动生成一个lunaKey
schema.__ctx = {
lunaKey: `luna${++scopeIdx}`,
};
scopeKey = schema.__ctx.lunaKey;
} else {
// 需要判断循环的情况
scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : '');
}
if (!this.__compScopes[scopeKey]) {
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
}
getSchemaChildren = (schema) => {
if (!schema || !schema.props) {
return schema?.children;
}
// 如果组件有设置scope需要为组件生成一个新的scope上下文
if (scopeKey && this.__compScopes[scopeKey]) {
const compSelf = { ...this.__compScopes[scopeKey] };
compSelf.__proto__ = self;
self = compSelf;
if (!schema.children) return schema.props.children;
if (!schema.props.children) return schema.children;
let _children = [].concat(schema.children);
if (Array.isArray(schema.props.children)) {
_children = _children.concat(schema.props.children);
} else {
_children.push(schema.props.children);
}
// 容器类组件的上下文通过props传递避免context传递带来的嵌套问题
const otherProps = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
}
const componentInfo = componentsMap[schema.componentName] || {};
const props = this.__parseProps(schema.props, self, '', {
schema,
Comp,
componentInfo: {
...componentInfo,
props: transformArrayToMap(componentInfo.props, 'name'),
},
});
// 对于可以获取到ref的组件做特殊处理
if (!acceptsRef(Comp)) {
Comp = compWrapper(Comp);
}
otherProps.ref = (ref) => {
this.$(props.fieldId, ref); // 收集ref
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
engine && engine.props.onCompGetRef(schema, ref);
};
// scope需要传入到组件上
if (scopeKey && this.__compScopes[scopeKey]) {
props.__scope = this.__compScopes[scopeKey];
}
if (schema.__ctx && schema.__ctx.lunaKey) {
if (!isFileSchema(schema)) {
engine && engine.props.onCompGetCtx(schema, self);
}
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
props.__id = schema.id;
if (!isFileSchema(schema) && schema.children) {
this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
self,
{
schema,
Comp,
},
);
}
const renderComp = (props) => engine.createElement(
Comp,
props,
(!isFileSchema(schema) &&
!!schema.children &&
this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
self,
{
schema,
Comp,
},
))
|| null,
);
// 设计模式下的特殊处理
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
// 对于overlay,dialog等组件为了使其在设计模式下显示外层需要增加一个div容器
if (OVERLAY_LIST.includes(schema.componentName)) {
const { ref, ...overlayProps } = otherProps;
return (
<div ref={ref} __designMode={engine.props.designMode}>
{renderComp({ ...props, ...overlayProps })}
</div>
);
}
// 虚拟dom显示
if (componentInfo && componentInfo.parentRule) {
const parentList = componentInfo.parentRule.split(',');
const { schema: parentSchema, Comp: parentComp } = parentInfo;
if (!parentList.includes(parentSchema.componentName) || parentComp !== components[parentSchema.componentName]) {
props.__componentName = schema.componentName;
Comp = VisualDom;
} else {
// 若虚拟dom在正常的渲染上下文中就不显示设计模式了
props.__disableDesignMode = true;
}
}
}
return renderComp({ ...props, ...otherProps });
return _children;
};
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {

View File

@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* requestHandlersMap 没有加到 appContext 里 ([a8d43c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8d43c3))
* simulator-renderer 补充丢失代码 ([67dd7e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/67dd7e2))
### Features
* 支持 build sourceMap, 方便用户调试 ([6bf75cd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6bf75cd))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-rax-simulator-renderer
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,4 +1,5 @@
{
"sourceMap": true,
"plugins": [
[
"build-plugin-component",

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-rax-simulator-renderer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "rax simulator renderer for alibaba lowcode designer",
"main": "lib/index.js",
"module": "es/index.js",
@ -13,10 +13,10 @@
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-rax-renderer": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-rax-renderer": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@ali/recore-rax": "^1.2.4",
"@ali/vu-css-style": "^1.0.2",
"@recore/obx": "^1.0.8",
@ -57,5 +57,5 @@
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
},
"homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-rax-simulator-renderer@1.0.24-beta.3/build/index.html"
"homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-rax-simulator-renderer@1.0.25-beta.1/build/index.html"
}

View File

@ -257,6 +257,9 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
// sync designMode
this._designMode = host.designMode;
// sync requestHandlersMap
this._requestHandlersMap = host.requestHandlersMap;
// sync device
this._device = host.device;
@ -318,6 +321,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
},
},
constants: {},
requestHandlersMap: this._requestHandlersMap,
};
host.injectionConsumer.consume((data) => {
// sync utils, i18n, contants,... config
@ -357,6 +361,10 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
@computed get device() {
return this._device;
}
@obx.ref private _requestHandlersMap = null;
@computed get requestHandlersMap(): any {
return this._requestHandlersMap;
}
@obx.ref private _componentsMap = {};
@computed get componentsMap(): any {
return this._componentsMap;

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-react-renderer
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-react-renderer
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-react-renderer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "react renderer for ali lowcode engine",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
### Bug Fixes
* requestHandlersMap 没有加到 appContext 里 ([a8d43c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8d43c3))
* simulator-renderer 补充丢失代码 ([67dd7e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/67dd7e2))
### Features
* 支持 build sourceMap, 方便用户调试 ([6bf75cd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6bf75cd))
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复 prop 无法删除最后一个 item ([e18a386](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e18a386))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,4 +1,5 @@
{
"sourceMap": true,
"plugins": [
[
"build-plugin-component",

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-react-simulator-renderer",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "react simulator renderer for alibaba lowcode designer",
"main": "lib/index.js",
"module": "es/index.js",
@ -16,10 +16,10 @@
"build": "build-scripts build --skip-demo"
},
"dependencies": {
"@ali/lowcode-designer": "^1.0.24-beta.4",
"@ali/lowcode-react-renderer": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-utils": "^1.0.24-beta.4",
"@ali/lowcode-designer": "^1.0.26-beta.0",
"@ali/lowcode-react-renderer": "^1.0.26-beta.0",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@ali/lowcode-utils": "^1.0.26-beta.0",
"@ali/vu-css-style": "^1.0.2",
"@recore/obx": "^1.0.8",
"@recore/obx-react": "^1.0.7",

View File

@ -1,6 +1,8 @@
import { Node } from '@ali/lowcode-designer';
import LowCodeRenderer from '@ali/lowcode-react-renderer';
import { ReactInstance, Fragment, Component, createElement } from 'react';
import { observer } from '@recore/obx-react';
import { isFromVC } from '@ali/lowcode-utils';
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
import { Router, Route, Switch } from 'react-router';
import './renderer.less';
@ -103,7 +105,7 @@ class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }
if (layout) {
const { Component, props, componentName } = layout;
if (Component) {
return <Component key='layout' props={props}>{children}</Component>;
return <Component key="layout" props={props}>{children}</Component>;
}
if (componentName && rendererContainer.getComponent(componentName)) {
return createElement(
@ -147,8 +149,10 @@ class Renderer extends Component<{
customCreateElement={(Component: any, props: any, children: any) => {
const { __id, __desingMode, ...viewProps } = props;
viewProps.componentId = __id;
const leaf = documentInstance.getNode(__id);
viewProps._leaf = leaf;
const leaf = documentInstance.getNode(__id) as Node;
if (isFromVC(leaf?.componentMeta)) {
viewProps._leaf = leaf;
}
viewProps._componentName = leaf?.componentName;
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
if (

View File

@ -211,6 +211,9 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
// sync designMode
this._designMode = host.designMode;
// sync requestHandlersMap
this._requestHandlersMap = host.requestHandlersMap;
// sync device
this._device = host.device;
});
@ -261,10 +264,11 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
getUrlParams() {
const search = history.location.search;
return parseQuery(search);
}
}
},
},
},
constants: {},
requestHandlersMap: this._requestHandlersMap,
};
host.injectionConsumer.consume((data) => {
// sync utils, i18n, contants,... config

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-types
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
**Note:** Version bump only for package @ali/lowcode-types
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-types",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Types for Ali lowCode engine",
"files": [
"es",

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.26-beta.0"></a>
## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22)
**Note:** Version bump only for package @ali/lowcode-utils
<a name="1.0.25-beta.1"></a>
## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15)
### Bug Fixes
* 修复 prop 无法删除最后一个 item ([e18a386](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e18a386))
<a name="1.0.24-beta.4"></a>
## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14)

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-utils",
"version": "1.0.24-beta.4",
"version": "1.0.26-beta.0",
"description": "Utils for Ali lowCode engine",
"files": [
"es",
@ -14,7 +14,7 @@
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@ali/lowcode-types": "^1.0.24-beta.4",
"@ali/lowcode-types": "^1.0.26-beta.0",
"@alifd/next": "^1.19.16",
"lodash.get": "^4.4.2",
"react": "^16"

View File

@ -1,6 +1,7 @@
import { isI18NObject } from './is-object';
import get from 'lodash.get';
import { ComponentMeta } from '@ali/lowcode-designer';
export function isUseI18NSetter(prototype: any, propName: string) {
const configure = prototype?.options?.configure;
@ -43,3 +44,11 @@ export function waitForThing(obj: any, path: string): Promise<any> {
}
return _innerWaitForThing(obj, path);
}
/**
* meta vc prototype
* @param meta
*/
export function isFromVC(meta: ComponentMeta) {
return !!meta?.getMetadata()?.experimental;
}