From 94fb4d2598ada46b1d94633c3150d3b8c3ebdd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=9B=E7=9A=93?= Date: Fri, 11 Dec 2020 15:29:49 +0800 Subject: [PATCH] =?UTF-8?q?chore(test):=20=E8=A1=A5=E5=85=85=20node-childr?= =?UTF-8?q?en=20/=20modal-nodes-manager=20=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/document/node/node-children.ts | 7 +- .../document/node/modal-nodes-manager.test.ts | 104 ++ .../tests/document/node/node-children.test.ts | 194 ++++ .../designer/tests/document/node/node.test.ts | 72 +- .../fixtures/component-metadata/button.ts | 279 +++++ .../fixtures/component-metadata/dialog.ts | 276 +++++ .../tests/fixtures/schema/form-with-modal.ts | 1021 +++++++++++++++++ packages/designer/tests/utils/misc.ts | 8 + 8 files changed, 1948 insertions(+), 13 deletions(-) create mode 100644 packages/designer/tests/document/node/modal-nodes-manager.test.ts create mode 100644 packages/designer/tests/document/node/node-children.test.ts create mode 100644 packages/designer/tests/fixtures/component-metadata/button.ts create mode 100644 packages/designer/tests/fixtures/component-metadata/dialog.ts create mode 100644 packages/designer/tests/fixtures/schema/form-with-modal.ts diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index b9cea8eab..142f13b41 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -219,8 +219,11 @@ export class NodeChildren { /** * */ - splice(start: number, deleteCount: number, node: Node): Node[] { - return this.children.splice(start, deleteCount, node); + splice(start: number, deleteCount: number, node?: Node): Node[] { + if (node) { + return this.children.splice(start, deleteCount, node); + } + return this.children.splice(start, deleteCount); } /** diff --git a/packages/designer/tests/document/node/modal-nodes-manager.test.ts b/packages/designer/tests/document/node/modal-nodes-manager.test.ts new file mode 100644 index 000000000..df4869a78 --- /dev/null +++ b/packages/designer/tests/document/node/modal-nodes-manager.test.ts @@ -0,0 +1,104 @@ +import '../../fixtures/window'; +import { set } from '../../utils'; +import { Editor } 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 formSchema from '../../fixtures/schema/form-with-modal'; +import divMetadata from '../../fixtures/component-metadata/div'; +import dlgMetadata from '../../fixtures/component-metadata/dialog'; +import buttonMetadata from '../../fixtures/component-metadata/button'; +import formMetadata from '../../fixtures/component-metadata/form'; +import otherMeta from '../../fixtures/component-metadata/other'; +import pageMetadata from '../../fixtures/component-metadata/page'; +import rootHeaderMetadata from '../../fixtures/component-metadata/root-header'; +import rootContentMetadata from '../../fixtures/component-metadata/root-content'; +import rootFooterMetadata from '../../fixtures/component-metadata/root-footer'; +import { delayObxTick, delay } from '../../utils'; + +describe('ModalNodesManager 方法测试', () => { + let editor: Editor; + let designer: Designer; + let project: Project; + let doc: DocumentModel; + + beforeEach(() => { + editor = new Editor(); + designer = new Designer({ editor }); + designer.createComponentMeta(dlgMetadata); + project = designer.project; + doc = new DocumentModel(project, formSchema); + }); + + afterEach(() => { + project.unload(); + designer.purge(); + editor = null; + designer = null; + project = null; + }); + + it('getModalNodes / getVisibleModalNode', () => { + const mgr = doc.modalNodesManager; + const nodes = mgr.getModalNodes(); + expect(nodes).toHaveLength(1); + expect(nodes[0].componentName).toBe('Dialog'); + expect(mgr.getVisibleModalNode()).toBeFalsy(); + }); + + it('setVisible / setInvisible / onVisibleChange', () => { + const mgr = doc.modalNodesManager; + const nodes = mgr.getModalNodes(); + + const mockFn = jest.fn(); + const off = mgr.onVisibleChange(mockFn); + + mgr.setVisible(nodes[0]); + expect(mockFn).toHaveBeenCalledTimes(2); + + mgr.setInvisible(nodes[0]); + expect(mockFn).toHaveBeenCalledTimes(3); + + off(); + }); + + it('addNode / removeNode', () => { + const mgr = doc.modalNodesManager!; + const nodes = mgr.getModalNodes(); + + const nodesMockFn = jest.fn(); + const visibleMockFn = jest.fn(); + const off = mgr.onModalNodesChange(nodesMockFn); + const offVisible = mgr.onVisibleChange(visibleMockFn); + + const newNode = new Node(doc, { componentName: 'Dialog' }) + mgr.addNode(newNode); + expect(visibleMockFn).toHaveBeenCalledTimes(2); + expect(nodesMockFn).toHaveBeenCalledTimes(1); + + mgr.setVisible(newNode); + mgr.removeNode(newNode); + + expect(visibleMockFn).toHaveBeenCalledTimes(6); + expect(nodesMockFn).toHaveBeenCalledTimes(2); + + off(); + offVisible(); + visibleMockFn.mockClear(); + nodesMockFn.mockClear(); + + mgr.addNode(newNode); + expect(visibleMockFn).not.toHaveBeenCalled(); + expect(nodesMockFn).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/designer/tests/document/node/node-children.test.ts b/packages/designer/tests/document/node/node-children.test.ts new file mode 100644 index 000000000..1f368855e --- /dev/null +++ b/packages/designer/tests/document/node/node-children.test.ts @@ -0,0 +1,194 @@ +import '../../fixtures/window'; +import { set } from '../../utils'; +import { Editor } 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 formSchema from '../../fixtures/schema/form'; +import divMetadata from '../../fixtures/component-metadata/div'; +import buttonMetadata from '../../fixtures/component-metadata/button'; +import formMetadata from '../../fixtures/component-metadata/form'; +import otherMeta from '../../fixtures/component-metadata/other'; +import pageMetadata from '../../fixtures/component-metadata/page'; +import rootHeaderMetadata from '../../fixtures/component-metadata/root-header'; +import rootContentMetadata from '../../fixtures/component-metadata/root-content'; +import rootFooterMetadata from '../../fixtures/component-metadata/root-footer'; +import { delayObxTick, delay } from '../../utils'; + +describe('NodeChildren 方法测试', () => { + let editor: Editor; + let designer: Designer; + let project: Project; + let doc: DocumentModel; + + beforeEach(() => { + editor = new Editor(); + designer = new Designer({ editor }); + project = designer.project; + doc = new DocumentModel(project, formSchema); + }); + + afterEach(() => { + project.unload(); + designer.purge(); + editor = null; + designer = null; + project = null; + }); + + it('isEmpty / notEmpty', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const secondBtn = doc.getNode('node_k1ow3cbp')!; + const { children } = firstBtn.parent!; + + expect(children.isEmpty()).toBeFalsy(); + expect(children.notEmpty()).toBeTruthy(); + expect(firstBtn.children.notEmpty()).toBeFalsy(); + expect(firstBtn.children.isEmpty()).toBeTruthy(); + }); + + it('purge / for of', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + children.purge(); + + for (const child of children) { + expect(child.isPurged).toBeTruthy(); + } + }); + + it('splice', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + children.splice(0, 1); + + expect(children.size).toBe(1); + expect(children.length).toBe(1); + + children.splice(0, 0, { componentName: 'Button' }); + expect(children.size).toBe(2); + expect(children.length).toBe(2); + }); + + it('map', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + const newMap = children.map((item) => item); + + newMap?.forEach((item) => { + expect(item.componentName).toBe('Button'); + }); + }); + + it('forEach', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + children.forEach((item) => { + expect(item.componentName).toBe('Button'); + }); + }); + + it('some', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + children.some((item) => { + expect(item.componentName).toBe('Button'); + }); + }); + + it('every', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + children.every((item) => { + expect(item.componentName).toBe('Button'); + }); + }); + + it('filter', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + children + .filter((item) => item.componentName === 'Button') + .forEach((item) => { + expect(item.componentName).toBe('Button'); + }); + }); + + it('find', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + const found = children.find((item) => item.componentName === 'Button'); + + expect(found?.componentName).toBe('Button'); + }); + + it('mergeChildren', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + const changeMockFn = jest.fn(); + const offChange = children.onChange(changeMockFn); + const rmMockFn = jest.fn((item) => { + if (item.index === 1) return true; + return false; + }); + const addMockFn = jest.fn((children) => { + return [{ componentName: 'Button' }, { componentName: 'Button' }]; + }); + const sortMockFn = jest.fn((a, b) => { + return a > b ? 1 : -1; + }); + children.mergeChildren(rmMockFn, addMockFn, sortMockFn); + + expect(children.size).toBe(3); + expect(changeMockFn).toHaveBeenCalled(); + offChange(); + }); + + it('insert / onInsert', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + const mockFn = jest.fn(); + const off = children.onInsert(mockFn); + + children.insert(new Node(doc, { componentName: 'Button' })); + expect(mockFn).toHaveBeenCalledTimes(1); + off(); + children.insert(new Node(doc, { componentName: 'Button' })); + expect(mockFn).toHaveBeenCalledTimes(1); + }); + + it('reportModified', () => { + const divMeta = designer.createComponentMeta(divMetadata); + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const { children } = firstBtn.parent!; + + const modifiedMockFn = jest.fn(); + divMeta.getMetadata = () => { + return { experimental: { callbacks: { onSubtreeModified: modifiedMockFn } } }; + }; + + children.reportModified(null); + children.reportModified(doc.rootNode); + + children.reportModified(firstBtn, firstBtn.parent); + expect(modifiedMockFn).toHaveBeenCalled(); + }); +}); diff --git a/packages/designer/tests/document/node/node.test.ts b/packages/designer/tests/document/node/node.test.ts index efb329a26..130a2518d 100644 --- a/packages/designer/tests/document/node/node.test.ts +++ b/packages/designer/tests/document/node/node.test.ts @@ -22,7 +22,7 @@ import pageMetadata from '../../fixtures/component-metadata/page'; import rootHeaderMetadata from '../../fixtures/component-metadata/root-header'; import rootContentMetadata from '../../fixtures/component-metadata/root-content'; import rootFooterMetadata from '../../fixtures/component-metadata/root-footer'; -import { nodeTopFixedReducer } from 'editor-preset-vision/src/props-reducers'; +import { delayObxTick, delay } from '../../utils'; describe('Node 方法测试', () => { let editor: Editor; @@ -189,7 +189,7 @@ describe('Node 方法测试', () => { }); }); - it('removeChild / replaceWith / replaceChild / insert / insertBefore / insertAfter / onChildrenChange / mergeChildren', () => { + it('removeChild / replaceWith / replaceChild / onChildrenChange / mergeChildren', () => { const firstBtn = doc.getNode('node_k1ow3cbn')!; firstBtn.select(); @@ -204,7 +204,56 @@ describe('Node 方法测试', () => { expect(firstBtn.parent?.getChildren()?.get(1)?.getPropValue('y')).toBe(1); }); - it.only('setVisible / getVisible / onVisibleChange', () => { + describe('插入相关方法', () => { + it('insertBefore / onChildrenChange', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const secondBtn = doc.getNode('node_k1ow3cbp')!; + const btnParent = firstBtn.parent!; + const mockFn = jest.fn(); + const off = btnParent.onChildrenChange(mockFn); + + // Node 实例 + btnParent.insertBefore(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn); + expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ a: 1 }); + expect(mockFn).toHaveBeenCalledTimes(1); + + // TODO: 暂时不支持,后面补上 + // // NodeSchema + // btnParent.insertBefore({ componentName: 'Button', props: { b: 1 } }, firstBtn); + // expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ b: 1 }); + // expect(mockFn).toHaveBeenCalledTimes(2); + + // // getComponentName + // btnParent.insertBefore({ getComponentName: () => 'Button', props: { c: 1 } }, firstBtn); + // expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ c: 1 }); + // expect(mockFn).toHaveBeenCalledTimes(3); + }); + + it('insertAfter / onChildrenChange', () => { + const firstBtn = doc.getNode('node_k1ow3cbn')!; + const secondBtn = doc.getNode('node_k1ow3cbp')!; + const btnParent = firstBtn.parent!; + const mockFn = jest.fn(); + const off = btnParent.onChildrenChange(mockFn); + + // Node 实例 + btnParent.insertAfter(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn); + expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ a: 1 }); + expect(mockFn).toHaveBeenCalledTimes(1); + + // NodeSchema + btnParent.insertAfter({ componentName: 'Button', props: { b: 1 } }, firstBtn); + expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ b: 1 }); + expect(mockFn).toHaveBeenCalledTimes(2); + + // getComponentName + btnParent.insertAfter({ getComponentName: () => 'Button' }, firstBtn); + expect(btnParent.children.get(1)?.getProps().export().props).toBeUndefined(); + expect(mockFn).toHaveBeenCalledTimes(3); + }); + }); + + it('setVisible / getVisible / onVisibleChange', async () => { const mockFn = jest.fn(); const firstBtn = doc.getNode('node_k1ow3cbn')!; const off = firstBtn.onVisibleChange(mockFn); @@ -213,20 +262,17 @@ describe('Node 方法测试', () => { expect(mockFn).toHaveBeenCalledTimes(1); expect(mockFn).toHaveBeenCalledWith(true); - // TODO: 此处 stash 转成了非 stash,需要再研究下 firstBtn.setVisible(false); - console.log(firstBtn.getExtraProp('hidden')); - console.log(firstBtn.getExtraProp('hidden', false)); - // console.log(firstBtn.getExtraProp('hidden', false)?.getValue()); - // console.log(firstBtn.getVisible()); - // expect(firstBtn.getVisible()).toBeFalsy(); - // expect(mockFn).toHaveBeenCalledTimes(2); - // expect(mockFn).toHaveBeenCalledWith(false); + await delayObxTick(); + expect(firstBtn.getVisible()).toBeFalsy(); + expect(mockFn).toHaveBeenCalledTimes(2); + expect(mockFn).toHaveBeenCalledWith(false); off(); mockFn.mockClear(); firstBtn.setVisible(true); + await delayObxTick(); expect(mockFn).not.toHaveBeenCalled(); }); @@ -363,6 +409,10 @@ describe('Node 方法测试', () => { }); it('getZLevelTop', () => {}); + it('propsData', () => { + expect(new Node(doc, { componentName: 'Leaf' }).propsData).toBeNull(); + expect(new Node(doc, { componentName: 'Fragment' }).propsData).toBeNull(); + }); describe('deprecated methods', () => { it('setStatus / getStatus', () => { diff --git a/packages/designer/tests/fixtures/component-metadata/button.ts b/packages/designer/tests/fixtures/component-metadata/button.ts new file mode 100644 index 000000000..f521d82f6 --- /dev/null +++ b/packages/designer/tests/fixtures/component-metadata/button.ts @@ -0,0 +1,279 @@ +export default { + componentName: 'Button', + npm: { + package: '@ali/vc-button', + componentName: 'Button', + }, + title: '按钮', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + devMode: 'procode', + tags: ['布局'], + configure: { + props: [ + { + type: 'field', + name: 'behavior', + title: '默认状态', + extraProps: { + display: 'inline', + defaultValue: 'NORMAL', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: '__style__', + title: { + label: '样式设置', + tip: '点击 ? 查看样式设置器用法指南', + docUrl: 'https://lark.alipay.com/legao/help/design-tool-style', + }, + extraProps: { + display: 'accordion', + defaultValue: {}, + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + name: 'groupkgzzeo41', + title: '高级', + extraProps: { + display: 'accordion', + }, + items: [ + { + type: 'field', + name: 'fieldId', + title: { + label: '唯一标识', + }, + extraProps: { + display: 'block', + }, + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'useFieldIdAsDomId', + title: { + label: '将唯一标识用作 DOM ID', + }, + extraProps: { + display: 'block', + defaultValue: false, + }, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + type: 'field', + name: 'customClassName', + title: '自定义样式类', + extraProps: { + display: 'block', + defaultValue: '', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: 'events', + title: { + label: '动作设置', + tip: '点击 ? 查看如何设置组件的事件响应动作', + docUrl: 'https://lark.alipay.com/legao/legao/events-call', + }, + extraProps: { + display: 'accordion', + defaultValue: { + ignored: true, + }, + }, + setter: { + key: null, + ref: null, + props: { + events: [ + { + name: 'onClick', + title: '当点击时', + initialValue: + "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}", + }, + { + name: 'onMouseEnter', + title: '当鼠标进入时', + initialValue: + "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}", + }, + { + name: 'onMouseLeave', + title: '当鼠标离开时', + initialValue: + "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}", + }, + ], + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'onClick', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseEnter', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseLeave', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + ], + }, + ], + component: { + isContainer: true, + nestingRule: { + parentWhitelist: 'Div', + childWhitelist: 'Div', + }, + }, + supports: {}, + }, + experimental: { + callbacks: {}, + initials: [ + { + name: 'behavior', + }, + { + name: '__style__', + }, + { + name: 'fieldId', + }, + { + name: 'useFieldIdAsDomId', + }, + { + name: 'customClassName', + }, + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + filters: [ + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + autoruns: [], + }, +}; diff --git a/packages/designer/tests/fixtures/component-metadata/dialog.ts b/packages/designer/tests/fixtures/component-metadata/dialog.ts new file mode 100644 index 000000000..e0d389f4d --- /dev/null +++ b/packages/designer/tests/fixtures/component-metadata/dialog.ts @@ -0,0 +1,276 @@ +export default { + componentName: 'Dialog', + npm: { + package: '@ali/vc-dialog', + componentName: 'Dialog', + }, + title: '容器', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + devMode: 'procode', + tags: ['布局'], + configure: { + props: [ + { + type: 'field', + name: 'behavior', + title: '默认状态', + extraProps: { + display: 'inline', + defaultValue: 'NORMAL', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: '__style__', + title: { + label: '样式设置', + tip: '点击 ? 查看样式设置器用法指南', + docUrl: 'https://lark.alipay.com/legao/help/design-tool-style', + }, + extraProps: { + display: 'accordion', + defaultValue: {}, + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + name: 'groupkgzzeo41', + title: '高级', + extraProps: { + display: 'accordion', + }, + items: [ + { + type: 'field', + name: 'fieldId', + title: { + label: '唯一标识', + }, + extraProps: { + display: 'block', + }, + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'useFieldIdAsDomId', + title: { + label: '将唯一标识用作 DOM ID', + }, + extraProps: { + display: 'block', + defaultValue: false, + }, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + type: 'field', + name: 'customClassName', + title: '自定义样式类', + extraProps: { + display: 'block', + defaultValue: '', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: 'events', + title: { + label: '动作设置', + tip: '点击 ? 查看如何设置组件的事件响应动作', + docUrl: 'https://lark.alipay.com/legao/legao/events-call', + }, + extraProps: { + display: 'accordion', + defaultValue: { + ignored: true, + }, + }, + setter: { + key: null, + ref: null, + props: { + events: [ + { + name: 'onClick', + title: '当点击时', + initialValue: + "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}", + }, + { + name: 'onMouseEnter', + title: '当鼠标进入时', + initialValue: + "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}", + }, + { + name: 'onMouseLeave', + title: '当鼠标离开时', + initialValue: + "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}", + }, + ], + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'onClick', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseEnter', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseLeave', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + ], + }, + ], + component: { + isContainer: true, + isModal: true, + }, + supports: {}, + }, + experimental: { + callbacks: {}, + initials: [ + { + name: 'behavior', + }, + { + name: '__style__', + }, + { + name: 'fieldId', + }, + { + name: 'useFieldIdAsDomId', + }, + { + name: 'customClassName', + }, + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + filters: [ + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + autoruns: [], + }, +}; diff --git a/packages/designer/tests/fixtures/schema/form-with-modal.ts b/packages/designer/tests/fixtures/schema/form-with-modal.ts new file mode 100644 index 000000000..0e0b4bfbd --- /dev/null +++ b/packages/designer/tests/fixtures/schema/form-with-modal.ts @@ -0,0 +1,1021 @@ +export default { + componentName: 'Page', + id: 'page', + title: "hey, i' a page!", + props: { + extensions: { + 启用页头: true, + }, + pageStyle: { + backgroundColor: '#f2f3f5', + }, + containerStyle: {}, + className: 'page_kgaqfbm4', + templateVersion: '1.0.0', + }, + lifeCycles: { + constructor: { + type: 'js', + compiled: + "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", + source: + "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", + }, + }, + condition: true, + css: + 'body{background-color:#f2f3f5}.card_kgaqfbm5 {\n margin-bottom: 12px;\n}.card_kgaqfbm6 {\n margin-bottom: 12px;\n}.button_kgaqfbm7 {\n margin-right: 16px;\n width: 80px\n}.button_kgaqfbm8 {\n width: 80px;\n}.div_kgaqfbm9 {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', + methods: { + __initMethods__: { + type: 'js', + source: 'function (exports, module) { /*set actions code here*/ }', + compiled: 'function (exports, module) { /*set actions code here*/ }', + }, + }, + dataSource: { + offline: [], + globalConfig: { + fit: { + compiled: '', + source: '', + type: 'js', + error: {}, + }, + }, + online: [], + sync: true, + list: [], + }, + children: [ + { + componentName: 'Dialog', + id: 'modal', + props: { + title: { + type: 'i18n', + use: 'zh_CN', + en_US: 'Dialog Title', + zh_CN: 'Dialog标题', + }, + visible: false, + hasMask: true, + closeable: 'esc', + autoFocus: true, + footer: true, + footerAlign: 'right', + footerActions: 'cancel,ok', + confirmText: { + type: 'i18n', + use: 'zh_CN', + en_US: 'Confirm', + zh_CN: '确定', + }, + cancelText: { + type: 'i18n', + use: 'zh_CN', + en_US: 'Cancel', + zh_CN: '取消', + }, + confirmStyle: 'primary', + confirmState: '确定', + __style__: {}, + fieldId: 'dialog_kijq2hni', + popupOutDialog: true, + }, + }, + { + componentName: 'RootHeader', + id: 'node_k1ow3cba', + props: {}, + condition: true, + children: [ + { + componentName: 'PageHeader', + id: 'node_k1ow3cbd', + props: { + extraContent: '', + __slot__extraContent: false, + __slot__action: false, + title: { + // type: 'JSSlot', + value: [ + { + componentName: 'Text', + id: 'node_k1ow3cbf', + props: { + showTitle: false, + behavior: 'NORMAL', + content: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '个人信息', + type: 'i18n', + }, + __style__: {}, + fieldId: 'text_k1ow3h1j', + maxLine: 0, + }, + condition: true, + }, + ], + }, + content: '', + __slot__logo: false, + __slot__crumb: false, + crumb: '', + tab: '', + logo: '', + action: '', + __slot__tab: false, + __style__: {}, + __slot__content: false, + fieldId: 'pageHeader_k1ow3h1i', + subTitle: false, + }, + condition: true, + }, + ], + }, + { + componentName: 'RootContent', + id: 'node_k1ow3cbb', + props: { + contentBgColor: 'transparent', + contentPadding: '0', + contentMargin: '20', + }, + condition: true, + children: [ + { + componentName: 'Form', + id: 'form', + extraPropA: 'extraPropA', + props: { + size: 'medium', + labelAlign: 'top', + autoValidate: true, + scrollToFirstError: true, + autoUnmount: true, + behavior: 'NORMAL', + dataSource: { + type: 'variable', + variable: 'state.formData', + }, + obj: { + a: 1, + b: false, + c: 'string', + }, + __style__: {}, + fieldId: 'form', + fieldOptions: {}, + slotA: '', + }, + condition: true, + children: [ + { + componentName: 'Card', + id: 'node_k1ow3cbj', + props: { + __slot__title: false, + subTitle: { + use: 'zh_CN', + en_US: '', + zh_CN: '', + type: 'i18n', + }, + __slot__subTitle: false, + extra: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + className: 'card_kgaqfbm5', + title: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '基本信息', + type: 'i18n', + }, + __slot__extra: false, + showHeadDivider: true, + __style__: ':root {\n margin-bottom: 12px;\n}', + showTitleBullet: true, + contentHeight: '', + fieldId: 'card_k1ow3h1l', + dividerNoInset: false, + }, + condition: true, + children: [ + { + componentName: 'CardContent', + id: 'node_k1ow3cbk', + props: {}, + condition: true, + children: [ + { + componentName: 'ColumnsLayout', + id: 'node_k1ow3cbw', + props: { + layout: '6:6', + columnGap: '20', + rowGap: 0, + __style__: {}, + fieldId: 'columns_k1ow3h1v', + }, + condition: true, + children: [ + { + componentName: 'Column', + id: 'node_k1ow3cbx', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjm', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cbz', + props: { + fieldName: 'name', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [ + { + type: 'required', + }, + ], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h1w', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '姓名', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + { + componentName: 'TextField', + id: 'node_k1ow3cc1', + props: { + fieldName: 'englishName', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h1y', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '英文名', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + { + componentName: 'TextField', + id: 'node_k1ow3cc3', + props: { + fieldName: 'jobTitle', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h20', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '职位', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + ], + }, + { + componentName: 'Column', + id: 'node_k1ow3cby', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjn', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc2', + props: { + fieldName: 'nickName', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h1z', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '花名', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + { + componentName: 'SelectField', + id: 'node_k1ow3cc0', + props: { + fieldName: 'gender', + hasClear: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + mode: 'single', + showSearch: false, + autoWidth: true, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please select', + zh_CN: '请选择', + type: 'i18n', + }, + hasBorder: true, + behavior: 'NORMAL', + value: '', + validation: [ + { + type: 'required', + }, + ], + __style__: {}, + fieldId: 'select_k1ow3h1x', + notFoundContent: { + use: 'zh_CN', + type: 'i18n', + }, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'SelectField', + zh_CN: '性别', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + wrapperColOffset: 0, + hasSelectAll: false, + hasArrow: true, + size: 'medium', + labelAlign: 'top', + filterLocal: true, + dataSource: [ + { + defaultChecked: false, + text: { + en_US: 'Option 1', + zh_CN: '男', + type: 'i18n', + __sid__: 'param_k1owc4tb', + }, + __sid__: 'serial_k1owc4t1', + value: 'M', + sid: 'opt_k1owc4t2', + }, + { + defaultChecked: false, + text: { + en_US: 'Option 2', + zh_CN: '女', + type: 'i18n', + __sid__: 'param_k1owc4tf', + }, + __sid__: 'serial_k1owc4t2', + value: 'F', + sid: 'opt_k1owc4t3', + }, + ], + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + searchDelay: 300, + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'Card', + id: 'node_k1ow3cbl', + props: { + __slot__title: false, + subTitle: { + use: 'zh_CN', + en_US: '', + zh_CN: '', + type: 'i18n', + }, + __slot__subTitle: false, + extra: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + className: 'card_kgaqfbm6', + title: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '部门信息', + type: 'i18n', + }, + __slot__extra: false, + showHeadDivider: true, + __style__: ':root {\n margin-bottom: 12px;\n}', + showTitleBullet: true, + contentHeight: '', + fieldId: 'card_k1ow3h1m', + dividerNoInset: false, + }, + condition: true, + children: [ + { + componentName: 'CardContent', + id: 'node_k1ow3cbm', + props: {}, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc4', + props: { + fieldName: 'department', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h21', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '所属部门', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + { + componentName: 'ColumnsLayout', + id: 'node_k1ow3cc5', + props: { + layout: '6:6', + columnGap: '20', + rowGap: 0, + __style__: {}, + fieldId: 'columns_k1ow3h22', + }, + condition: true, + children: [ + { + componentName: 'Column', + id: 'node_k1ow3cc6', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjo', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc8', + props: { + fieldName: 'leader', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h23', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '主管', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + ], + }, + { + componentName: 'Column', + id: 'node_k1ow3cc7', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjp', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc9', + props: { + fieldName: 'hrg', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h24', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: 'HRG', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: null, + zh_CN: '', + }, + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'Div', + id: 'node_k1ow3cbo', + props: { + className: 'div_kgaqfbm9', + behavior: 'NORMAL', + __style__: + ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', + events: {}, + fieldId: 'div_k1ow3h1o', + useFieldIdAsDomId: false, + customClassName: '', + }, + condition: true, + children: [ + { + componentName: 'Button', + id: 'node_k1ow3cbn', + props: { + triggerEventsWhenLoading: false, + onClick: { + rawType: 'events', + type: 'JSExpression', + value: 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])', + events: [ + { + name: 'submit', + id: 'submit', + params: {}, + type: 'actionRef', + uuid: '1570966253282_0', + }, + ], + }, + size: 'medium', + baseIcon: '', + otherIcon: '', + className: 'button_kgaqfbm7', + type: 'primary', + behavior: 'NORMAL', + loading: false, + content: { + use: 'zh_CN', + en_US: 'Button', + zh_CN: '提交', + type: 'i18n', + }, + __style__: ':root {\n margin-right: 16px;\n width: 80px\n}', + fieldId: 'button_k1ow3h1n', + }, + condition: true, + }, + { + componentName: 'Button', + id: 'node_k1ow3cbp', + props: { + triggerEventsWhenLoading: false, + size: 'medium', + baseIcon: '', + otherIcon: '', + className: 'button_kgaqfbm8', + type: 'normal', + behavior: 'NORMAL', + loading: false, + content: { + use: 'zh_CN', + en_US: 'Button', + zh_CN: '取消', + type: 'i18n', + }, + __style__: ':root {\n width: 80px;\n}', + fieldId: 'button_k1ow3h1p', + greeting: { + // type: 'JSSlot', + value: [ + { + componentName: 'Text', + props: {}, + }, + ], + }, + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'RootFooter', + id: 'node_k1ow3cbc', + props: {}, + condition: true, + }, + ], +}; diff --git a/packages/designer/tests/utils/misc.ts b/packages/designer/tests/utils/misc.ts index f640416a8..d93d85941 100644 --- a/packages/designer/tests/utils/misc.ts +++ b/packages/designer/tests/utils/misc.ts @@ -14,4 +14,12 @@ export function set(obj: any, path: any, val: any) { }) } return lodashSet(obj, path, val); +} + +export function delay(ms) { + return new Promise(resove => setTimeout(resove, ms)); +} + +export function delayObxTick() { + return delay(100); } \ No newline at end of file