diff --git a/CHANGELOG.md b/CHANGELOG.md index d08a7e4a9..69f510451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. + +## [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)) + + + + + +## [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)) + + + + ## [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) diff --git a/lerna.json b/lerna.json index 0b3948d13..2fad34c78 100644 --- a/lerna.json +++ b/lerna.json @@ -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, diff --git a/package.json b/package.json index fb4f2a481..87b025010 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/designer/CHANGELOG.md b/packages/designer/CHANGELOG.md index 99c411f2a..c6429c0f1 100644 --- a/packages/designer/CHANGELOG.md +++ b/packages/designer/CHANGELOG.md @@ -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. + +## [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)) + + + + + +## [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)) + + + + ## [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) diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index 1f136c9de..faaacb0f5 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -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/**', diff --git a/packages/designer/package.json b/packages/designer/package.json index f5baa6d14..0cd328eda 100644 --- a/packages/designer/package.json +++ b/packages/designer/package.json @@ -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", diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index fa1ee18e1..ebcdc46db 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -602,7 +602,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost !!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 { 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), { diff --git a/packages/designer/src/builtin-simulator/viewport.ts b/packages/designer/src/builtin-simulator/viewport.ts index 523933cc4..0362a1332 100644 --- a/packages/designer/src/builtin-simulator/viewport.ts +++ b/packages/designer/src/builtin-simulator/viewport.ts @@ -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); } diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 396ddbf55..6b41877ea 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -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); + } +} diff --git a/packages/designer/src/designer/active-tracker.ts b/packages/designer/src/designer/active-tracker.ts index 7b9509251..377d1a6b4 100644 --- a/packages/designer/src/designer/active-tracker.ts +++ b/packages/designer/src/designer/active-tracker.ts @@ -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; } diff --git a/packages/designer/src/designer/builtin-hotkey.ts b/packages/designer/src/designer/builtin-hotkey.ts index 27b33013d..d098a191d 100644 --- a/packages/designer/src/designer/builtin-hotkey.ts +++ b/packages/designer/src/designer/builtin-hotkey.ts @@ -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 { diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index d6fc63462..b41d08823 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -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(); diff --git a/packages/designer/src/designer/dragon.ts b/packages/designer/src/designer/dragon.ts index 358af49fb..322a6af8c 100644 --- a/packages/designer/src/designer/dragon.ts +++ b/packages/designer/src/designer/dragon.ts @@ -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 diff --git a/packages/designer/src/designer/scroller.ts b/packages/designer/src/designer/scroller.ts index 24070b306..6467fd494 100644 --- a/packages/designer/src/designer/scroller.ts +++ b/packages/designer/src/designer/scroller.ts @@ -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; } } } diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts index 4246a2423..805042440 100644 --- a/packages/designer/src/designer/setting/setting-prop-entry.ts +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -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; diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index 142f13b41..d8b4f3cf1 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -128,6 +128,8 @@ export class NodeChildren { slotNode.remove(useMutator, purge); }, (iterable, idx) => (iterable as [])[idx]); } + // 需要在从 children 中删除 node 前记录下 index,internalSetParent 中会执行删除(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; } diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 69a9d6570..f74c49219 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -937,7 +937,7 @@ export class Node { } if (this.parent) { - return this.parent.getSuitablePlace(node, ref); + return this.parent.getSuitablePlace(node, { index: this.index }); } return null; diff --git a/packages/designer/src/document/node/props/props.ts b/packages/designer/src/document/node/props/props.ts index 6a2ee2b22..ed44aa874 100644 --- a/packages/designer/src/document/node/props/props.ts +++ b/packages/designer/src/document/node/props/props.ts @@ -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 { diff --git a/packages/designer/tests/builtin-simulator/bem-tools/drag-resize-engine.test.ts b/packages/designer/tests/builtin-simulator/bem-tools/drag-resize-engine.test.ts new file mode 100644 index 000000000..b9622b015 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/bem-tools/drag-resize-engine.test.ts @@ -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 }); + }); +}); diff --git a/packages/designer/tests/builtin-simulator/host.test.ts b/packages/designer/tests/builtin-simulator/host.test.ts new file mode 100644 index 000000000..4c4e2672a --- /dev/null +++ b/packages/designer/tests/builtin-simulator/host.test.ts @@ -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, + ); + }); +}); diff --git a/packages/designer/tests/builtin-simulator/host.test.tsx b/packages/designer/tests/builtin-simulator/host.test.tsx deleted file mode 100644 index f181f7138..000000000 --- a/packages/designer/tests/builtin-simulator/host.test.tsx +++ /dev/null @@ -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, - ); - }) -}); diff --git a/packages/designer/tests/builtin-simulator/resource-consumer.test.ts b/packages/designer/tests/builtin-simulator/resource-consumer.test.ts new file mode 100644 index 000000000..5243112e1 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/resource-consumer.test.ts @@ -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 }); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/parse-metadata.test.ts b/packages/designer/tests/builtin-simulator/utils/parse-metadata.test.ts similarity index 59% rename from packages/designer/tests/builtin-simulator/parse-metadata.test.ts rename to packages/designer/tests/builtin-simulator/utils/parse-metadata.test.ts index 50a1ba005..e0c67255c 100644 --- a/packages/designer/tests/builtin-simulator/parse-metadata.test.ts +++ b/packages/designer/tests/builtin-simulator/utils/parse-metadata.test.ts @@ -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 () => { diff --git a/packages/designer/tests/builtin-simulator/path.test.ts b/packages/designer/tests/builtin-simulator/utils/path.test.ts similarity index 98% rename from packages/designer/tests/builtin-simulator/path.test.ts rename to packages/designer/tests/builtin-simulator/utils/path.test.ts index 170cb9f9f..be9ee6013 100644 --- a/packages/designer/tests/builtin-simulator/path.test.ts +++ b/packages/designer/tests/builtin-simulator/utils/path.test.ts @@ -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', () => { diff --git a/packages/designer/tests/builtin-simulator/throttle.test.ts b/packages/designer/tests/builtin-simulator/utils/throttle.test.ts similarity index 75% rename from packages/designer/tests/builtin-simulator/throttle.test.ts rename to packages/designer/tests/builtin-simulator/utils/throttle.test.ts index fd2d7ce33..ffbf6c886 100644 --- a/packages/designer/tests/builtin-simulator/throttle.test.ts +++ b/packages/designer/tests/builtin-simulator/utils/throttle.test.ts @@ -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)); diff --git a/packages/designer/tests/builtin-simulator/viewport.test.ts b/packages/designer/tests/builtin-simulator/viewport.test.ts new file mode 100644 index 000000000..ce3534353 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/viewport.test.ts @@ -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 }); + }); +}); diff --git a/packages/designer/tests/designer/active-tracker.test.ts b/packages/designer/tests/designer/active-tracker.test.ts new file mode 100644 index 000000000..1b73ecda5 --- /dev/null +++ b/packages/designer/tests/designer/active-tracker.test.ts @@ -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(); +}); \ No newline at end of file diff --git a/packages/designer/tests/designer/builtin-hotkey.test.ts b/packages/designer/tests/designer/builtin-hotkey.test.ts index 637c3cbfc..493aa5546 100644 --- a/packages/designer/tests/designer/builtin-hotkey.test.ts +++ b/packages/designer/tests/designer/builtin-hotkey.test.ts @@ -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'); + }); + }); }); diff --git a/packages/designer/tests/designer/designer.test.ts b/packages/designer/tests/designer/designer.test.ts new file mode 100644 index 000000000..0dbad8a83 --- /dev/null +++ b/packages/designer/tests/designer/designer.test.ts @@ -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); + }); +}); diff --git a/packages/designer/tests/designer/detecting.test.ts b/packages/designer/tests/designer/detecting.test.ts new file mode 100644 index 000000000..0e96e5bca --- /dev/null +++ b/packages/designer/tests/designer/detecting.test.ts @@ -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(); +}); \ No newline at end of file diff --git a/packages/designer/tests/designer/dragon.test.ts b/packages/designer/tests/designer/dragon.test.ts new file mode 100644 index 000000000..092ee4061 --- /dev/null +++ b/packages/designer/tests/designer/dragon.test.ts @@ -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(); + }); +}); diff --git a/packages/designer/tests/designer/location.test.ts b/packages/designer/tests/designer/location.test.ts new file mode 100644 index 000000000..cf748a7c8 --- /dev/null +++ b/packages/designer/tests/designer/location.test.ts @@ -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); +}); diff --git a/packages/designer/tests/designer/scroller.test.ts b/packages/designer/tests/designer/scroller.test.ts new file mode 100644 index 000000000..c6dcb6bb1 --- /dev/null +++ b/packages/designer/tests/designer/scroller.test.ts @@ -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 }); + }) + }); +}); diff --git a/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts b/packages/designer/tests/designer/setting/setting-prop-entry.test.ts similarity index 100% rename from packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts rename to packages/designer/tests/designer/setting/setting-prop-entry.test.ts diff --git a/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts b/packages/designer/tests/designer/setting/setting-top-entry.test.ts similarity index 99% rename from packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts rename to packages/designer/tests/designer/setting/setting-top-entry.test.ts index 906eb0720..f76501782 100644 --- a/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts +++ b/packages/designer/tests/designer/setting/setting-top-entry.test.ts @@ -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'); diff --git a/packages/designer/tests/document/node/node.test.ts b/packages/designer/tests/document/node/node.test.ts index 80d57dbb6..0416b2a67 100644 --- a/packages/designer/tests/document/node/node.test.ts +++ b/packages/designer/tests/document/node/node.test.ts @@ -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 }, }); }); diff --git a/packages/designer/tests/document/node/props/prop.test.ts b/packages/designer/tests/document/node/props/prop.test.ts new file mode 100644 index 000000000..ecc6023b4 --- /dev/null +++ b/packages/designer/tests/document/node/props/prop.test.ts @@ -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(); + }); +}); diff --git a/packages/designer/tests/document/node/props/props.test.ts b/packages/designer/tests/document/node/props/props.test.ts new file mode 100644 index 000000000..c72104072 --- /dev/null +++ b/packages/designer/tests/document/node/props/props.test.ts @@ -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'); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/fixtures/component-metadata/button.ts b/packages/designer/tests/fixtures/component-metadata/button.ts index f521d82f6..d0725efc3 100644 --- a/packages/designer/tests/fixtures/component-metadata/button.ts +++ b/packages/designer/tests/fixtures/component-metadata/button.ts @@ -221,10 +221,9 @@ export default { }, ], component: { - isContainer: true, nestingRule: { - parentWhitelist: 'Div', - childWhitelist: 'Div', + // parentWhitelist: 'Div', + // childWhitelist: 'Div', }, }, supports: {}, diff --git a/packages/designer/tests/fixtures/component-metadata/div.ts b/packages/designer/tests/fixtures/component-metadata/div.ts index ae1738754..941b90ef3 100644 --- a/packages/designer/tests/fixtures/component-metadata/div.ts +++ b/packages/designer/tests/fixtures/component-metadata/div.ts @@ -223,8 +223,8 @@ export default { component: { isContainer: true, nestingRule: { - parentWhitelist: 'Div', - childWhitelist: 'Div', + // parentWhitelist: 'Div', + // childWhitelist: 'Div', }, }, supports: {}, diff --git a/packages/designer/tests/fixtures/window.ts b/packages/designer/tests/fixtures/window.ts index d772d1ef4..4e0213d98 100644 --- a/packages/designer/tests/fixtures/window.ts +++ b/packages/designer/tests/fixtures/window.ts @@ -15,4 +15,6 @@ Object.defineProperty(window, 'matchMedia', { Object.defineProperty(window, 'React', { writable: true, value: {}, -}); \ No newline at end of file +}); + +window.scrollTo = () => {}; \ No newline at end of file diff --git a/packages/designer/tests/utils/bom.ts b/packages/designer/tests/utils/bom.ts index fca5f73fc..285000889 100644 --- a/packages/designer/tests/utils/bom.ts +++ b/packages/designer/tests/utils/bom.ts @@ -14,6 +14,7 @@ interface MockDocument extends Document { const eventsMap : Map> = new Map>(); +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; } \ No newline at end of file diff --git a/packages/designer/tests/utils/renderer.ts b/packages/designer/tests/utils/renderer.ts index 256a8c651..cd91c15c5 100644 --- a/packages/designer/tests/utils/renderer.ts +++ b/packages/designer/tests/utils/renderer.ts @@ -2,7 +2,7 @@ export function getMockRenderer() { return { isSimulatorRenderer: true, run() { - console.log('renderer run'); + // console.log('renderer run'); } } } \ No newline at end of file diff --git a/packages/editor-core/CHANGELOG.md b/packages/editor-core/CHANGELOG.md index 8091cd731..a2b81b8f5 100644 --- a/packages/editor-core/CHANGELOG.md +++ b/packages/editor-core/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/editor-core/package.json b/packages/editor-core/package.json index 2eda281d7..7fcb3c693 100644 --- a/packages/editor-core/package.json +++ b/packages/editor-core/package.json @@ -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", diff --git a/packages/editor-preset-general/CHANGELOG.md b/packages/editor-preset-general/CHANGELOG.md index b10e942cb..fbd5fa5fd 100644 --- a/packages/editor-preset-general/CHANGELOG.md +++ b/packages/editor-preset-general/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/editor-preset-general/package.json b/packages/editor-preset-general/package.json index 8ad0cff69..2d212f469 100644 --- a/packages/editor-preset-general/package.json +++ b/packages/editor-preset-general/package.json @@ -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", diff --git a/packages/editor-preset-vision/CHANGELOG.md b/packages/editor-preset-vision/CHANGELOG.md index 480c654e3..f4533e9c6 100644 --- a/packages/editor-preset-vision/CHANGELOG.md +++ b/packages/editor-preset-vision/CHANGELOG.md @@ -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. + +## [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)) + + + + + +## [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)) + + + + ## [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) diff --git a/packages/editor-preset-vision/build.json b/packages/editor-preset-vision/build.json index ffd529288..32d1da46c 100644 --- a/packages/editor-preset-vision/build.json +++ b/packages/editor-preset-vision/build.json @@ -1,4 +1,5 @@ { + "sourceMap": true, "plugins": [ [ "build-plugin-component", diff --git a/packages/editor-preset-vision/package.json b/packages/editor-preset-vision/package.json index 0c38a6a71..7953e1110 100644 --- a/packages/editor-preset-vision/package.json +++ b/packages/editor-preset-vision/package.json @@ -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", diff --git a/packages/editor-preset-vision/src/bundle/prototype.ts b/packages/editor-preset-vision/src/bundle/prototype.ts index eb93cb184..a42e93e36 100644 --- a/packages/editor-preset-vision/src/bundle/prototype.ts +++ b/packages/editor-preset-vision/src/bundle/prototype.ts @@ -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; diff --git a/packages/editor-preset-vision/src/index.ts b/packages/editor-preset-vision/src/index.ts index 2dcc762a0..52158b008 100644 --- a/packages/editor-preset-vision/src/index.ts +++ b/packages/editor-preset-vision/src/index.ts @@ -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, /** diff --git a/packages/editor-preset-vision/src/panes.ts b/packages/editor-preset-vision/src/panes.ts index 840211188..d33d9a77f 100644 --- a/packages/editor-preset-vision/src/panes.ts +++ b/packages/editor-preset-vision/src/panes.ts @@ -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); }; }, /** diff --git a/packages/editor-skeleton/CHANGELOG.md b/packages/editor-skeleton/CHANGELOG.md index b9ce6298e..4540ad346 100644 --- a/packages/editor-skeleton/CHANGELOG.md +++ b/packages/editor-skeleton/CHANGELOG.md @@ -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. + +## [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 + + +## [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)) + + + + ## [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) diff --git a/packages/editor-skeleton/package.json b/packages/editor-skeleton/package.json index 86d4555b6..4ff9fba31 100644 --- a/packages/editor-skeleton/package.json +++ b/packages/editor-skeleton/package.json @@ -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", diff --git a/packages/editor-skeleton/src/widget/panel.ts b/packages/editor-skeleton/src/widget/panel.ts index 40fbaead4..8ecacc43e 100644 --- a/packages/editor-skeleton/src/widget/panel.ts +++ b/packages/editor-skeleton/src/widget/panel.ts @@ -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; } diff --git a/packages/editor-skeleton/src/widget/widget-container.ts b/packages/editor-skeleton/src/widget/widget-container.ts index 013268db8..dceeabc5a 100644 --- a/packages/editor-skeleton/src/widget/widget-container.ts +++ b/packages/editor-skeleton/src/widget/widget-container.ts @@ -79,6 +79,10 @@ export default class WidgetContainer this.unactive(name)); + } + add(item: T | G): T { item = this.handle(item); const origin = this.get(item.name); diff --git a/packages/ignitor/CHANGELOG.md b/packages/ignitor/CHANGELOG.md index ca7326dcc..5a921a83c 100644 --- a/packages/ignitor/CHANGELOG.md +++ b/packages/ignitor/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/ignitor/package.json b/packages/ignitor/package.json index 8b057130e..34692758f 100644 --- a/packages/ignitor/package.json +++ b/packages/ignitor/package.json @@ -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, diff --git a/packages/plugin-designer/CHANGELOG.md b/packages/plugin-designer/CHANGELOG.md index 30cdcefc1..ca516d861 100644 --- a/packages/plugin-designer/CHANGELOG.md +++ b/packages/plugin-designer/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/plugin-designer/package.json b/packages/plugin-designer/package.json index f7c2e911e..f799d2018 100644 --- a/packages/plugin-designer/package.json +++ b/packages/plugin-designer/package.json @@ -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" }, diff --git a/packages/plugin-outline-pane/CHANGELOG.md b/packages/plugin-outline-pane/CHANGELOG.md index 8756fecec..8df17dbba 100644 --- a/packages/plugin-outline-pane/CHANGELOG.md +++ b/packages/plugin-outline-pane/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/plugin-outline-pane/package.json b/packages/plugin-outline-pane/package.json index 3b96342c9..4e00dc602 100644 --- a/packages/plugin-outline-pane/package.json +++ b/packages/plugin-outline-pane/package.json @@ -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", diff --git a/packages/rax-render/CHANGELOG.md b/packages/rax-render/CHANGELOG.md index 40a07101e..07b365584 100644 --- a/packages/rax-render/CHANGELOG.md +++ b/packages/rax-render/CHANGELOG.md @@ -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. - + +## [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)) + + + + + +## [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 + + ## [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 - + ## [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) diff --git a/packages/rax-render/package.json b/packages/rax-render/package.json index 36c77be0e..d3fe2816a 100644 --- a/packages/rax-render/package.json +++ b/packages/rax-render/package.json @@ -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", diff --git a/packages/rax-render/src/comp/Div.ts b/packages/rax-render/src/comp/Div.ts new file mode 100644 index 000000000..8db0dbd15 --- /dev/null +++ b/packages/rax-render/src/comp/Div.ts @@ -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); + } +} diff --git a/packages/rax-render/src/engine/base.tsx b/packages/rax-render/src/engine/base.tsx index f097a147f..c87a688da 100644 --- a/packages/rax-render/src/engine/base.tsx +++ b/packages/rax-render/src/engine/base.tsx @@ -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 ( +
+ {renderComp({ ...props, ...overlayProps })} +
+ ); + } + // 虚拟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 ( -
- {renderComp({ ...props, ...overlayProps })} -
- ); - } - // 虚拟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) => { diff --git a/packages/rax-simulator-renderer/CHANGELOG.md b/packages/rax-simulator-renderer/CHANGELOG.md index 9737c2763..266405338 100644 --- a/packages/rax-simulator-renderer/CHANGELOG.md +++ b/packages/rax-simulator-renderer/CHANGELOG.md @@ -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. + +## [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)) + + + + + +## [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 + ## [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) diff --git a/packages/rax-simulator-renderer/build.json b/packages/rax-simulator-renderer/build.json index 70f118bdf..59f913b34 100644 --- a/packages/rax-simulator-renderer/build.json +++ b/packages/rax-simulator-renderer/build.json @@ -1,4 +1,5 @@ { + "sourceMap": true, "plugins": [ [ "build-plugin-component", diff --git a/packages/rax-simulator-renderer/package.json b/packages/rax-simulator-renderer/package.json index 7f51d7cd6..0dc9eb257 100644 --- a/packages/rax-simulator-renderer/package.json +++ b/packages/rax-simulator-renderer/package.json @@ -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" } diff --git a/packages/rax-simulator-renderer/src/renderer.ts b/packages/rax-simulator-renderer/src/renderer.ts index febfdb2cb..80441d474 100644 --- a/packages/rax-simulator-renderer/src/renderer.ts +++ b/packages/rax-simulator-renderer/src/renderer.ts @@ -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; diff --git a/packages/react-renderer/CHANGELOG.md b/packages/react-renderer/CHANGELOG.md index c29ebd6c4..e15b58fa9 100644 --- a/packages/react-renderer/CHANGELOG.md +++ b/packages/react-renderer/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/react-renderer/package.json b/packages/react-renderer/package.json index 41648d43c..adbce0fcf 100644 --- a/packages/react-renderer/package.json +++ b/packages/react-renderer/package.json @@ -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", diff --git a/packages/react-simulator-renderer/CHANGELOG.md b/packages/react-simulator-renderer/CHANGELOG.md index e0f914f14..3a2cee7a2 100644 --- a/packages/react-simulator-renderer/CHANGELOG.md +++ b/packages/react-simulator-renderer/CHANGELOG.md @@ -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. + +## [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)) + + + + + +## [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)) + + + + ## [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) diff --git a/packages/react-simulator-renderer/build.json b/packages/react-simulator-renderer/build.json index 12c3c10c6..cc6d56cd4 100644 --- a/packages/react-simulator-renderer/build.json +++ b/packages/react-simulator-renderer/build.json @@ -1,4 +1,5 @@ { + "sourceMap": true, "plugins": [ [ "build-plugin-component", diff --git a/packages/react-simulator-renderer/package.json b/packages/react-simulator-renderer/package.json index 5a86d5f05..3173ae318 100644 --- a/packages/react-simulator-renderer/package.json +++ b/packages/react-simulator-renderer/package.json @@ -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", diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index aa758fe16..263a71e56 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -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 {children}; + return {children}; } 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 ( diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index 79453ad0b..7bf47106e 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -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 diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 49bbd0f2e..0dad341e3 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -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. + +## [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 + + +## [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 + ## [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) diff --git a/packages/types/package.json b/packages/types/package.json index 514de44d8..f032c6377 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -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", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 22b48bc14..55a31949c 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -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. + +## [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 + + +## [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)) + + + + ## [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) diff --git a/packages/utils/package.json b/packages/utils/package.json index 57ed2f6fe..d378ee2e3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -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" diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index a0f8f13ff..5428be93a 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -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 { } return _innerWaitForThing(obj, path); } + +/** + * 判断当前 meta 是否从 vc prototype 转换而来 + * @param meta + */ +export function isFromVC(meta: ComponentMeta) { + return !!meta?.getMetadata()?.experimental; +} \ No newline at end of file