From cd12677138a223eaf2b1579d27d3bf0addc1565e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=9B=E7=9A=93?= Date: Fri, 13 Nov 2020 15:22:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9A=B4=E9=9C=B2=20registerMetadataTr?= =?UTF-8?q?ansducer=20=E6=8E=A5=E5=8F=A3=20chore(test):=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/designer/jest.config.js | 1 + .../builtin-simulator/utils/parse-metadata.ts | 2 +- .../src/designer/setting/setting-top-entry.ts | 2 +- .../builtin-simulator/parse-metadata.test.ts | 9 + .../tests/builtin-simulator/path.test.ts | 78 +++++ .../tests/builtin-simulator/throttle.test.ts | 22 ++ .../tests/fixtures/component-metadata/div.ts | 5 +- .../designer/tests/fixtures/disable-raf.ts | 3 + .../tests/fixtures/prototype/div-meta.ts | 259 ++++++++++++++++ .../designer/tests/fixtures/schema/setting.ts | 90 ++++++ .../tests/meta/component-meta.test.ts | 45 ++- .../setting-entry/setting-prop-entry.test.ts | 105 +++++++ .../setting-entry/setting-top-entry.test.ts | 174 +++++++++++ packages/editor-preset-vision/jest.config.js | 3 + packages/editor-preset-vision/src/index.ts | 3 + .../src/props-reducers/init-node-reducer.ts | 2 +- .../tests/bundle/bundle.test.ts | 116 +++++++ .../tests/bundle/prototype.test.ts | 16 + .../tests/bundle/trunk.test.ts | 111 +++++++ .../fixtures/prototype/div-vision-full.ts | 293 ++++++++++++++++++ .../tests/fixtures/schema/form.ts | 2 +- .../tests/props-reducer/init-node.test.ts | 0 .../tests/vision-api/exchange.test.ts | 18 ++ .../tests/vision-api/pages.test.ts | 11 +- .../tests/vision-api/project.test.ts | 42 +-- packages/editor-skeleton/.eslintrc.js | 1 + .../settings/settings-primary-pane.tsx | 8 +- 27 files changed, 1382 insertions(+), 39 deletions(-) create mode 100644 packages/designer/tests/builtin-simulator/parse-metadata.test.ts create mode 100644 packages/designer/tests/builtin-simulator/path.test.ts create mode 100644 packages/designer/tests/builtin-simulator/throttle.test.ts create mode 100644 packages/designer/tests/fixtures/disable-raf.ts create mode 100644 packages/designer/tests/fixtures/prototype/div-meta.ts create mode 100644 packages/designer/tests/fixtures/schema/setting.ts create mode 100644 packages/designer/tests/setting-entry/setting-prop-entry.test.ts create mode 100644 packages/designer/tests/setting-entry/setting-top-entry.test.ts create mode 100644 packages/editor-preset-vision/tests/bundle/bundle.test.ts create mode 100644 packages/editor-preset-vision/tests/bundle/trunk.test.ts create mode 100644 packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts create mode 100644 packages/editor-preset-vision/tests/props-reducer/init-node.test.ts create mode 100644 packages/editor-preset-vision/tests/vision-api/exchange.test.ts diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index 83eb1dc90..b1c191127 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -14,6 +14,7 @@ module.exports = { collectCoverage: false, collectCoverageFrom: [ 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', '!**/node_modules/**', '!**/vendor/**', ], diff --git a/packages/designer/src/builtin-simulator/utils/parse-metadata.ts b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts index 3bcf9d6d4..96c92943a 100644 --- a/packages/designer/src/builtin-simulator/utils/parse-metadata.ts +++ b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts @@ -54,7 +54,7 @@ const LowcodeTypes: any = { (window as any).PropTypes = LowcodeTypes; (window as any).React.PropTypes = LowcodeTypes; -// override primitive type chechers +// override primitive type checkers primitiveTypes.forEach(type => { const propType = (PropTypes as any)[type]; if (!propType) { diff --git a/packages/designer/src/designer/setting/setting-top-entry.ts b/packages/designer/src/designer/setting/setting-top-entry.ts index 507fc053c..405c76faa 100644 --- a/packages/designer/src/designer/setting/setting-top-entry.ts +++ b/packages/designer/src/designer/setting/setting-top-entry.ts @@ -68,7 +68,7 @@ export class SettingTopEntry implements SettingEntry { readonly designer: Designer; constructor(readonly editor: IEditor, readonly nodes: Node[]) { - if (nodes.length < 1) { + if (!Array.isArray(nodes) || nodes.length < 1) { throw new ReferenceError('nodes should not be empty'); } this.id = generateSessionId(nodes); diff --git a/packages/designer/tests/builtin-simulator/parse-metadata.test.ts b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts new file mode 100644 index 000000000..eefb55f7c --- /dev/null +++ b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts @@ -0,0 +1,9 @@ +import '../fixtures/window'; +import { parseMetadata } from '../../src/builtin-simulator/utils/parse-metadata'; + +describe('parseMetadata', () => { + it('parseMetadata', async () => { + console.log(parseMetadata('Div')) + console.log(parseMetadata({ componentName: 'Div' })); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/path.test.ts b/packages/designer/tests/builtin-simulator/path.test.ts new file mode 100644 index 000000000..6a15edbcf --- /dev/null +++ b/packages/designer/tests/builtin-simulator/path.test.ts @@ -0,0 +1,78 @@ +import { + generateComponentName, + getNormalizedImportPath, + isPackagePath, + toTitleCase, + makeRelativePath, + removeVersion, + resolveAbsoluatePath, + joinPath, +} from '../../src/builtin-simulator/utils/path'; + +describe('builtin-simulator/utils/path 测试', () => { + it('isPackagePath', () => { + expect(isPackagePath('a')).toBeTruthy; + expect(isPackagePath('@ali/a')).toBeTruthy; + expect(isPackagePath('@alife/a')).toBeTruthy; + expect(isPackagePath('a.b')).toBeTruthy; + expect(isPackagePath('./a')).toBeFalsy; + expect(isPackagePath('../a')).toBeFalsy; + expect(isPackagePath('/a')).toBeFalsy; + }); + + it('toTitleCase', () => { + expect(toTitleCase('a')).toBe('A'); + expect(toTitleCase('a_b')).toBe('AB'); + expect(toTitleCase('a b')).toBe('AB'); + expect(toTitleCase('a-b')).toBe('AB'); + expect(toTitleCase('a.b')).toBe('AB'); + expect(toTitleCase('a.b.cx')).toBe('ABCx'); + }); + + it('generateComponentName', () => { + expect(generateComponentName('a/index.js')).toBe('A'); + expect(generateComponentName('a_b/index.js')).toBe('AB'); + expect(generateComponentName('a_b/index.web.js')).toBe('AB'); + expect(generateComponentName('a_b/index.xxx.js')).toBe('AB'); + expect(generateComponentName('a_b')).toBe('AB'); + expect(generateComponentName('')).toBe('Component'); + }); + + it('getNormalizedImportPath', () => { + expect(getNormalizedImportPath('/a')).toBe('/a'); + expect(getNormalizedImportPath('/a/')).toBe('/a/'); + expect(getNormalizedImportPath('/a/index.js')).toBe('/a'); + expect(getNormalizedImportPath('/a/index.ts')).toBe('/a'); + expect(getNormalizedImportPath('/a/index.jsx')).toBe('/a'); + expect(getNormalizedImportPath('/a/index.tsx')).toBe('/a'); + expect(getNormalizedImportPath('/a/index.x')).toBe('/a/index.x'); + }); + + it('makeRelativePath', () => { + expect(makeRelativePath('/a/b/c', '/a/b')).toBe('c'); + expect(makeRelativePath('a/b/c', '/a/c')).toBe('a/b/c'); + expect(makeRelativePath('/a/b/c', '/a/c')).toBe('./b/c'); + expect(makeRelativePath('/a/b/c', '/a/c/d')).toBe('../b/c'); + }); + + it('resolveAbsoluatePath', () => { + expect(resolveAbsoluatePath('/a/b/c', '/a')).toBe('/a/b/c'); + expect(resolveAbsoluatePath('@ali/fe', '/a')).toBe('@ali/fe'); + expect(resolveAbsoluatePath('./a/b', '/c')).toBe('/c/a/b'); + expect(resolveAbsoluatePath('./a/b/d', '/c')).toBe('/c/a/b/d'); + expect(resolveAbsoluatePath('../a/b', '/c')).toBe('/a/b'); + expect(resolveAbsoluatePath('../a/b/d', '/c')).toBe('/a/b/d'); + expect(resolveAbsoluatePath('../../a', 'c')).toBe('../a'); + }); + + it('joinPath', () => { + expect(joinPath('/a', 'b', 'c')).toBe('/a/b/c'); + expect(joinPath('a', 'b', 'c')).toBe('./a/b/c'); + }); + + it('removeVersion', () => { + expect(removeVersion('@ali/fe')).toBe('@ali/fe'); + expect(removeVersion('@ali/fe@1.0.0/index')).toBe('@ali/fe/index'); + expect(removeVersion('haha')).toBe('haha'); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/throttle.test.ts b/packages/designer/tests/builtin-simulator/throttle.test.ts new file mode 100644 index 000000000..fd2d7ce33 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/throttle.test.ts @@ -0,0 +1,22 @@ +import '../fixtures/disable-raf'; +import { throttle } from '../../src/builtin-simulator/utils/throttle'; + +const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +const cb = jest.fn(); + +describe('throttle', () => { + it('simple', async () => { + const fn = throttle(cb, 1000); + fn(); + + expect(cb).toBeCalledTimes(1); + + await delay(200); + fn(); + + await delay(400); + fn(); + expect(cb).toBeCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/fixtures/component-metadata/div.ts b/packages/designer/tests/fixtures/component-metadata/div.ts index 27bcd18fc..395cb58bb 100644 --- a/packages/designer/tests/fixtures/component-metadata/div.ts +++ b/packages/designer/tests/fixtures/component-metadata/div.ts @@ -218,7 +218,10 @@ export default { ], component: { isContainer: true, - nestingRule: {}, + nestingRule: { + parentWhitelist: 'Div', + childWhitelist: 'Div', + }, }, supports: {}, }, diff --git a/packages/designer/tests/fixtures/disable-raf.ts b/packages/designer/tests/fixtures/disable-raf.ts new file mode 100644 index 000000000..14b710125 --- /dev/null +++ b/packages/designer/tests/fixtures/disable-raf.ts @@ -0,0 +1,3 @@ +Object.defineProperty(window, 'requestAnimationFrame', { + value: null, +}) \ No newline at end of file diff --git a/packages/designer/tests/fixtures/prototype/div-meta.ts b/packages/designer/tests/fixtures/prototype/div-meta.ts new file mode 100644 index 000000000..a2b410494 --- /dev/null +++ b/packages/designer/tests/fixtures/prototype/div-meta.ts @@ -0,0 +1,259 @@ +export default { + componentName: 'Div', + 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: 'groupkh97h5kc', + 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: {}, + }, + supports: {}, + }, + experimental: { + callbacks: {}, + initials: [ + { + name: 'behavior', + }, + { + name: '__style__', + }, + { + name: 'fieldId', + }, + { + name: 'useFieldIdAsDomId', + }, + { + name: 'customClassName', + }, + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + filters: [], + autoruns: [], + }, +}; diff --git a/packages/designer/tests/fixtures/schema/setting.ts b/packages/designer/tests/fixtures/schema/setting.ts new file mode 100644 index 000000000..a0d45d0d9 --- /dev/null +++ b/packages/designer/tests/fixtures/schema/setting.ts @@ -0,0 +1,90 @@ +export default { + componentName: 'Page', + id: 'node_k1ow3cb9', + 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*/ }', + }, + }, + children: [ + { + componentName: 'Div', + id: 'div', + 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: { + type: 'JSExpression', + value: 'getFromSomewhere()' + }, + customClassName2: { + type: 'JSExpression', + mock: { hi: 'mock' }, + value: 'getFromSomewhere()' + }, + }, + extraPropA: 'haha', + }, + { + componentName: 'Div', + id: 'div2', + 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: '', + }, + extraPropA: 'haha', + }, + { + componentName: 'Test', + id: 'test', + 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: '', + }, + extraPropA: 'haha', + } + ], +}; diff --git a/packages/designer/tests/meta/component-meta.test.ts b/packages/designer/tests/meta/component-meta.test.ts index cec01ba5f..f3cdfd2a6 100644 --- a/packages/designer/tests/meta/component-meta.test.ts +++ b/packages/designer/tests/meta/component-meta.test.ts @@ -4,7 +4,7 @@ import '../fixtures/window'; import { Node } from '../../src/document/node/node'; import { Designer } from '../../src/designer/designer'; import divMeta from '../fixtures/component-metadata/div'; -import { ComponentMeta } from '../../src/component-meta'; +import { ComponentMeta, isComponentMeta, removeBuiltinComponentAction, addBuiltinComponentAction } from '../../src/component-meta'; const mockCreateSettingEntry = jest.fn(); jest.mock('../../src/designer/designer', () => { @@ -25,6 +25,47 @@ beforeAll(() => { describe('组件元数据处理', () => { it('构造函数', () => { const meta = new ComponentMeta(designer, divMeta); - console.log(meta); + expect(meta.isContainer).toBeTruthy; + expect(isComponentMeta(meta)).toBeTruthy; + expect(meta.acceptable).toBeTruthy; + expect(meta.isRootComponent()).toBeTruthy; + expect(meta.isModal).toBeFalsy; + expect(meta.rootSelector).toBeUndefined; + expect(meta.liveTextEditing).toBeUndefined; + expect(meta.descriptor).toBeUndefined; + expect(meta.icon).toBeUndefined; + expect(meta.getMetadata().title).toBe('容器'); + expect(meta.title).toEqual({ type: 'i18n', 'en-US': 'Div', 'zh-CN': '容器' }); + + meta.setNpm({ package: '@ali/vc-div', componentName: 'Div' }); + expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' }); + + meta.setMetadata(divMeta); + }); + + it('availableActions', () => { + const meta = new ComponentMeta(designer, divMeta); + expect(meta.availableActions).toHaveLength(3); + expect(meta.availableActions[0].name).toBe('remove'); + expect(meta.availableActions[1].name).toBe('hide'); + expect(meta.availableActions[2].name).toBe('copy'); + + removeBuiltinComponentAction('remove'); + // availableActions 有 computed 缓存 + expect(meta.availableActions[0].name).toBe('remove'); + expect(meta.availableActions[1].name).toBe('hide'); + expect(meta.availableActions[2].name).toBe('copy'); + + addBuiltinComponentAction({ + name: 'new', + content: { + action() {} + } + }); + // availableActions 有 computed 缓存 + expect(meta.availableActions).toHaveLength(3); + expect(meta.availableActions[0].name).toBe('remove'); + expect(meta.availableActions[1].name).toBe('hide'); + expect(meta.availableActions[2].name).toBe('copy'); }); }); \ No newline at end of file diff --git a/packages/designer/tests/setting-entry/setting-prop-entry.test.ts b/packages/designer/tests/setting-entry/setting-prop-entry.test.ts new file mode 100644 index 000000000..1169dc70d --- /dev/null +++ b/packages/designer/tests/setting-entry/setting-prop-entry.test.ts @@ -0,0 +1,105 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import { Editor } from '@ali/lowcode-editor-core'; +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 settingSchema from '../fixtures/schema/setting'; +import divMeta from '../fixtures/prototype/div-meta'; +import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; + +const editor = new Editor(); + +describe('setting-prop-entry 测试', () => { + let designer: Designer; + beforeEach(() => { + designer = new Designer({ editor }); + }); + afterEach(() => { + designer._componentMetasMap.clear(); + designer = null; + }); + + describe('node 构造函数生成 settingEntry', () => { + it.only('常规方法测试', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + + const { settingEntry } = divNode!; + const behaviorProp = settingEntry.getProp('behavior'); + expect(behaviorProp.getProps()).toBe(settingEntry); + expect(behaviorProp.props).toBe(settingEntry); + expect(behaviorProp.getName()).toBe('behavior'); + expect(behaviorProp.getKey()).toBe('behavior'); + expect(behaviorProp.isIgnore()).toBeFalsy; + behaviorProp.setKey('behavior2'); + expect(behaviorProp.getKey()).toBe('behavior2'); + behaviorProp.setKey('behavior'); + expect(behaviorProp.getValue()).toBe('NORMAL'); + expect(behaviorProp.getMockOrValue()).toBe('NORMAL'); + + behaviorProp.setValue('LARGE'); + expect(behaviorProp.getValue()).toBe('LARGE'); + // behaviorProp.setPropValue('behavior', 'SMALL'); + // expect(behaviorProp.getValue()).toBe('SMALL'); + behaviorProp.setValue('NORMAL'); + expect(behaviorProp.getValue()).toBe('NORMAL'); + + behaviorProp.clearValue(); + behaviorProp.clearPropValue(); + expect(settingEntry.getProp('behavior').getValue()).toBeUndefined; + + behaviorProp.setValue('LARGE'); + expect(behaviorProp.getValue()).toBe('LARGE'); + behaviorProp.remove(); + expect(settingEntry.getProp('behavior').getValue()).toBeUndefined; + + expect(behaviorProp.getNode()).toBe(divNode); + expect(behaviorProp.getId().startsWith('entry')).toBeTruthy; + expect(behaviorProp.designer).toBe(designer); + expect(behaviorProp.isSingle).toBeTruthy; + expect(behaviorProp.isMultiple).toBeFalsy; + expect(behaviorProp.isGroup).toBeFalsy; + expect(behaviorProp.isSameComponent).toBeTruthy; + expect(typeof settingEntry.getValue).toBe('function'); + settingEntry.getValue(); + + behaviorProp.setExtraPropValue('extraPropA', 'heihei'); + expect(behaviorProp.getExtraPropValue('extraPropA', 'heihei')); + }); + + it.skip('type: group 场景测试', () => { + + }); + + it('JSExpression 类型的 prop', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + + const { settingEntry } = divNode!; + const customClassNameProp = settingEntry.getProp('customClassName'); + expect(customClassNameProp.isUseVariable()).toBeTruthy; + expect(customClassNameProp.useVariable).toBeTruthy; + + expect(customClassNameProp.getValue()).toEqual({ + type: 'JSExpression', + value: 'getFromSomewhere()' + }); + expect(customClassNameProp.getMockOrValue()).toBeUndefined; + expect(customClassNameProp.getVariableValue()).toBe('getFromSomewhere()'); + customClassNameProp.setVariableValue('xxx'); + expect(customClassNameProp.getVariableValue()).toBe('xxx'); + + const customClassName2Prop = settingEntry.getProp('customClassName2'); + expect(customClassName2Prop.getMockOrValue()).toEqual({ + hi: 'mock', + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/setting-entry/setting-top-entry.test.ts b/packages/designer/tests/setting-entry/setting-top-entry.test.ts new file mode 100644 index 000000000..435993926 --- /dev/null +++ b/packages/designer/tests/setting-entry/setting-top-entry.test.ts @@ -0,0 +1,174 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import { Editor } from '@ali/lowcode-editor-core'; +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 settingSchema from '../fixtures/schema/setting'; +import divMeta from '../fixtures/prototype/div-meta'; +import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; + +const editor = new Editor(); + +describe('setting-top-entry 测试', () => { + let designer: Designer; + beforeEach(() => { + designer = new Designer({ editor }); + }); + afterEach(() => { + designer._componentMetasMap.clear(); + designer = null; + }); + + describe('node 构造函数生成 settingEntry', () => { + it('常规方法测试', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + + const { settingEntry } = divNode!; + expect(settingEntry.getPropValue('behavior')).toBe('NORMAL'); + expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL'); + settingEntry.setPropValue('behavior', 'LARGE'); + expect(settingEntry.getPropValue('behavior')).toBe('LARGE'); + expect(settingEntry.get('behavior').getValue()).toBe('LARGE'); + settingEntry.getProp('behavior').setValue('SMALL'); + expect(settingEntry.getPropValue('behavior')).toBe('SMALL'); + settingEntry.clearPropValue('behavior'); + expect(settingEntry.getPropValue('behavior')).toBeUndefined; + + expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o'); + settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new'); + expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new'); + + expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha'); + settingEntry.setExtraPropValue('extraPropA', 'haha2'); + expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2'); + + settingEntry.mergeProps({ + newPropA: 'haha', + }); + expect(settingEntry.getPropValue('newPropA')).toBe('haha'); + settingEntry.setProps({ + newPropB: 'haha', + }); + expect(settingEntry.getPropValue('newPropB')).toBe('haha'); + settingEntry.setValue({ + newPropC: 'haha', + }); + expect(settingEntry.getPropValue('newPropC')).toBe('haha'); + + expect(settingEntry.getPage()).toBe(currentDocument); + expect(settingEntry.getNode()).toBe(divNode); + expect(settingEntry.node).toBe(divNode); + expect(settingEntry.getId()).toBe('div'); + expect(settingEntry.first).toBe(divNode); + expect(settingEntry.designer).toBe(designer); + expect(settingEntry.isSingle).toBeTruthy; + expect(settingEntry.isMultiple).toBeFalsy; + expect(settingEntry.isSameComponent).toBeTruthy; + + expect(typeof settingEntry.getValue).toBe('function'); + settingEntry.getValue(); + }); + + it('清理方法测试', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + + const { settingEntry } = divNode!; + expect(settingEntry.items).toHaveLength(3); + settingEntry.purge(); + expect(settingEntry.items).toHaveLength(0); + }); + + it('vision 兼容测试', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + + console.log(divNode?.getPropValue('behavior')); + const { settingEntry } = divNode!; + + expect(typeof settingEntry.getChildren).toBe('function'); + expect(typeof settingEntry.getDOMNode).toBe('function'); + expect(typeof settingEntry.getStatus).toBe('function'); + expect(typeof settingEntry.setStatus).toBe('function'); + settingEntry.getStatus(); + settingEntry.setStatus(); + settingEntry.getChildren(); + settingEntry.getDOMNode(); + }); + + it('没有 node', () => { + const create1 = designer.createSettingEntry.bind(designer); + const create2 = designer.createSettingEntry.bind(designer, []); + expect(create1).toThrowError('nodes should not be empty'); + expect(create2).toThrowError('nodes should not be empty'); + }); + }); + + describe('designer.createSettingEntry 生成 settingEntry(多 node 场景)', () => { + it('相同类型的 node', () => { + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + const divNode2 = currentDocument?.getNode('div2'); + const settingEntry = designer.createSettingEntry([divNode, divNode2]); + + expect(settingEntry.isMultiple).toBeTruthy; + expect(settingEntry.isSameComponent).toBeTruthy; + expect(settingEntry.isSingle).toBeFalsy; + + expect(settingEntry.getPropValue('behavior')).toBe('NORMAL'); + expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL'); + settingEntry.setPropValue('behavior', 'LARGE'); + expect(settingEntry.getPropValue('behavior')).toBe('LARGE'); + expect(settingEntry.get('behavior').getValue()).toBe('LARGE'); + // 多个 node 都被成功设值 + expect(divNode?.getPropValue('behavior')).toBe('LARGE'); + expect(divNode2?.getPropValue('behavior')).toBe('LARGE'); + + settingEntry.getProp('behavior').setValue('SMALL'); + expect(settingEntry.getPropValue('behavior')).toBe('SMALL'); + // 多个 node 都被成功设值 + expect(divNode?.getPropValue('behavior')).toBe('SMALL'); + expect(divNode2?.getPropValue('behavior')).toBe('SMALL'); + + settingEntry.clearPropValue('behavior'); + expect(settingEntry.getPropValue('behavior')).toBeUndefined; + // 多个 node 都被成功设值 + expect(divNode?.getPropValue('behavior')).toBeUndefined; + expect(divNode2?.getPropValue('behavior')).toBeUndefined; + + expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o'); + settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new'); + expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new'); + + expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha'); + settingEntry.setExtraPropValue('extraPropA', 'haha2'); + expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2'); + }); + + it('不同类型的 node', () => { + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div'); + const testNode = currentDocument?.getNode('test'); + const settingEntry = designer.createSettingEntry([divNode, testNode]); + + expect(settingEntry.isMultiple).toBeTruthy; + expect(settingEntry.isSameComponent).toBeFalsy; + expect(settingEntry.isSingle).toBeFalsy; + + // 不同类型的 node 场景下,理论上从页面上已没有修改属性的方法调用,所以此处不再断言各设值方法 + // 思考:假如以后面向其他场景,比如用户用 API 强行调用,是否需要做健壮性保护? + }); + }); +}); \ No newline at end of file diff --git a/packages/editor-preset-vision/jest.config.js b/packages/editor-preset-vision/jest.config.js index 2cbe0c3fe..5b9f59ab5 100644 --- a/packages/editor-preset-vision/jest.config.js +++ b/packages/editor-preset-vision/jest.config.js @@ -17,6 +17,9 @@ module.exports = { collectCoverage: false, collectCoverageFrom: [ 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/base/**', + '!src/prop.ts', '!**/node_modules/**', '!**/vendor/**', ], diff --git a/packages/editor-preset-vision/src/index.ts b/packages/editor-preset-vision/src/index.ts index 47a684711..2bade9959 100644 --- a/packages/editor-preset-vision/src/index.ts +++ b/packages/editor-preset-vision/src/index.ts @@ -5,6 +5,7 @@ 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 { createElement } from 'react'; import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const'; import Bus from './bus'; @@ -110,6 +111,7 @@ const VisualEngine = { Project, logger, Symbols, + registerMetadataTransducer, // Flags, }; @@ -160,6 +162,7 @@ export { Project, logger, Symbols, + registerMetadataTransducer, }; const version = '6.0.0 (LowcodeEngine 0.9.32)'; diff --git a/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts index 72e7f4e5d..031482f46 100644 --- a/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts +++ b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts @@ -9,7 +9,6 @@ import { isJSExpression, isJSBlock, isJSSlot } from '@ali/lowcode-types'; import { isVariable, getCurrentFieldIds } from '../utils'; export function initNodeReducer(props, node) { - // debugger; // run initials const newProps: any = { ...props, @@ -23,6 +22,7 @@ export function initNodeReducer(props, node) { } } const initials = node.componentMeta.getMetadata().experimental?.initials; + if (initials) { const getRealValue = (propValue: any) => { if (isVariable(propValue)) { diff --git a/packages/editor-preset-vision/tests/bundle/bundle.test.ts b/packages/editor-preset-vision/tests/bundle/bundle.test.ts new file mode 100644 index 000000000..31174d481 --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/bundle.test.ts @@ -0,0 +1,116 @@ +import { Component } from 'react'; +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import trunk from '../../src/bundle/trunk'; +import Prototype from '../../src/bundle/prototype'; +import Bundle from '../../src/bundle/bundle'; +import { Editor } from '@ali/lowcode-editor-core'; + +jest.mock('../../src/bundle/trunk', () => { + // mockComponentPrototype = jest.fn(); + // return { + // mockComponentPrototype: jest.fn().mockImplementation(() => { + // return proto; + // }), + // } + // return jest.fn().mockImplementation(() => { + // return {playSoundFile: fakePlaySoundFile}; + // }); + // return jest.fn().mockImplementation(() => { + // return { mockComponentPrototype }; + // }); + return { + __esModule: true, + default: { + mockComponentPrototype: jest.fn(), + }, + }; +}); + +function wrap(name, thing) { + return { + name, + componentName: name, + category: '布局', + module: thing, + }; +} + +const proto1 = new Prototype(divPrototypeConfig); +const protoConfig2 = cloneDeep(divPrototypeConfig); +set(protoConfig2, 'componentName', 'Div2'); +const proto2 = new Prototype(protoConfig2); + +const protoConfig3 = cloneDeep(divPrototypeConfig); +set(protoConfig3, 'componentName', 'Div3'); +const proto3 = new Prototype(protoConfig3); + +const protoConfig4 = cloneDeep(divPrototypeConfig); +set(protoConfig4, 'componentName', 'Div4'); +const proto4 = new Prototype(protoConfig4); + +const protoConfig5 = cloneDeep(divPrototypeConfig); +set(protoConfig5, 'componentName', 'Div5'); +const proto5 = new Prototype(protoConfig5); + +function getComponentProtos() { + return [ + wrap('Div', proto1), + // wrap('Div2', proto2), + // wrap('Div3', proto3), + wrap('DivPortal', [proto2, proto3]), + ]; +} + +class Div extends Component {} +Div.displayName = 'Div'; +class Div2 extends Component {} +Div2.displayName = 'Div2'; +class Div3 extends Component {} +Div3.displayName = 'Div3'; +class Div4 extends Component {} +Div4.displayName = 'Div4'; +class Div5 extends Component {} +Div5.displayName = 'Div5'; + +function getComponentViews() { + return [ + wrap('Div', Div), + // wrap('Div2', Div2), + // wrap('Div3', Div3), + wrap('DivPortal', [Div2, Div3]), + ]; +} + +describe('Bundle', () => { + it('构造函数', () => { + const protos = getComponentProtos(); + const views = getComponentViews(); + const bundle = new Bundle(protos, views); + expect(bundle.getList()).toHaveLength(3); + expect(bundle.get('Div')).toBe(proto1); + expect(bundle.get('Div2')).toBe(proto2); + expect(bundle.get('Div3')).toBe(proto3); + bundle.addComponentBundle([proto4, Div4]); + expect(bundle.getList()).toHaveLength(4); + expect(bundle.get('Div4')).toBe(proto4); + bundle.replacePrototype('Div4', proto3); + expect(proto3.getView()).toBe(Div4); + + bundle.removeComponentBundle('Div2'); + expect(bundle.getList()).toHaveLength(3); + expect(bundle.get('Div2')).toBeUndefined; + + expect(bundle.getFromMeta('Div')).toBe(proto1); + bundle.getFromMeta('Div5'); + expect(bundle.getList()).toHaveLength(4); + }); + it('静态方法 create', () => { + const protos = getComponentProtos(); + const views = getComponentViews(); + const bundle = Bundle.create(protos, views); + expect(bundle).toBeTruthy(); + }); +}); diff --git a/packages/editor-preset-vision/tests/bundle/prototype.test.ts b/packages/editor-preset-vision/tests/bundle/prototype.test.ts index cc9667563..4776ebb1e 100644 --- a/packages/editor-preset-vision/tests/bundle/prototype.test.ts +++ b/packages/editor-preset-vision/tests/bundle/prototype.test.ts @@ -6,6 +6,7 @@ import '../fixtures/window'; // import { Node } from '../../src/document/node/node'; // import { Designer } from '../../src/designer/designer'; import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import divFullPrototypeConfig from '../fixtures/prototype/div-vision-full'; import divPrototypeMeta from '../fixtures/prototype/div-meta'; // import VisualEngine from '../../src'; import { designer } from '../../src/editor'; @@ -29,6 +30,21 @@ describe('Prototype', () => { expect(proto.isContainer()).toBeTruthy; expect(proto.isModal()).toBeFalsy; }); + it('构造函数 - 全量 OldPrototypeConfig', () => { + const proto = new Prototype(divFullPrototypeConfig); + expect(isPrototype(proto)).toBeTruthy; + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); it('构造函数 - ComponentMetadata', () => { const proto = new Prototype(divPrototypeMeta); expect(proto.getComponentName()).toBe('Div'); diff --git a/packages/editor-preset-vision/tests/bundle/trunk.test.ts b/packages/editor-preset-vision/tests/bundle/trunk.test.ts new file mode 100644 index 000000000..d4bca021f --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/trunk.test.ts @@ -0,0 +1,111 @@ +import { Component } from 'react'; +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import Prototype from '../../src/bundle/prototype'; +import Bundle from '../../src/bundle/bundle'; +import trunk from '../../src/bundle/trunk'; +import lg from '@ali/vu-logger'; + +const proto1 = new Prototype(divPrototypeConfig); +const protoConfig2 = cloneDeep(divPrototypeConfig); +set(protoConfig2, 'componentName', 'Div2'); +const proto2 = new Prototype(protoConfig2); + +const protoConfig3 = cloneDeep(divPrototypeConfig); +set(protoConfig3, 'componentName', 'Div3'); +const proto3 = new Prototype(protoConfig3); + +const mockComponentPrototype = jest.fn(); +jest.mock('../../src/bundle/bundle', () => { + // return { + // mockComponentPrototype: jest.fn().mockImplementation(() => { + // return proto; + // }), + // } + // return jest.fn().mockImplementation(() => { + // return {playSoundFile: fakePlaySoundFile}; + // }); + return jest.fn().mockImplementation(() => { + return { + get: () => {}, + getList: () => { return []; }, + getFromMeta: () => {}, + }; + }); +}); + +const mockError = jest.fn(); +jest.mock('@ali/vu-logger'); +lg.error = mockError; + +function wrap(name, thing) { + return { + name, + componentName: name, + category: '布局', + module: thing, + }; +} + +function getComponentProtos() { + return [ + wrap('Div', proto1), + // wrap('Div2', proto2), + // wrap('Div3', proto3), + wrap('DivPortal', [proto2, proto3]), + ]; +} + +class Div extends Component {} +Div.displayName = 'Div'; +class Div2 extends Component {} +Div2.displayName = 'Div2'; +class Div3 extends Component {} +Div3.displayName = 'Div3'; + +function getComponentViews() { + return [ + wrap('Div', Div), + // wrap('Div2', Div2), + // wrap('Div3', Div3), + wrap('DivPortal', [Div2, Div3]), + ]; +} + +describe('Trunk', () => { + it('构造函数', () => { + const warn = console.warn = jest.fn(); + const trunkChangeHandler = jest.fn(); + const off = trunk.onTrunkChange(trunkChangeHandler); + trunk.addBundle(new Bundle([proto1], [Div])); + trunk.addBundle(new Bundle([proto2], [Div2])); + expect(trunkChangeHandler).toHaveBeenCalledTimes(2); + off(); + trunk.addBundle(new Bundle([proto3], [Div3])); + expect(trunkChangeHandler).toHaveBeenCalledTimes(2); + trunk.getList(); + trunk.getPrototype('Div'); + trunk.getPrototypeById('Div'); + trunk.getPrototypeView('Div'); + trunk.listByCategory(); + expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBeUndefined; + expect(mockError).toHaveBeenCalled(); + trunk.registerComponentPrototypeMocker({ mockPrototype: jest.fn().mockImplementation(() => { return proto3; }) }); + expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBe(proto3); + const hahaSetter = () => 'haha'; + trunk.registerSetter('haha', hahaSetter); + expect(trunk.getSetter('haha')).toBe(hahaSetter); + trunk.getRecents(5); + trunk.setPackages(); + expect(warn).toHaveBeenCalledTimes(1); + trunk.beforeLoadBundle(); + expect(warn).toHaveBeenCalledTimes(2); + trunk.afterLoadBundle(); + expect(warn).toHaveBeenCalledTimes(3); + trunk.getBundle(); + expect(warn).toHaveBeenCalledTimes(4); + expect(trunk.isReady()).toBeTruthy; + }); +}); diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts new file mode 100644 index 000000000..756c37649 --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts @@ -0,0 +1,293 @@ +export default { + title: '容器', + componentName: 'Div', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + category: '布局', + isContainer: true, + canOperating: false, + extraActions: [], + canContain: 'Form', + canDropTo: 'Div', + canDropIn: 'Div', + canResizing: true, + canDraging: false, + context: {}, + initialChildren() {}, + didDropIn() {}, + didDropOut() {}, + subtreeModified() {}, + onResize() {}, + onResizeStart() {}, + onResizeEnd() {}, + canUseCondition: true, + canLoop: true, + snippets: [ + { + screenshot: 'https://img.alicdn.com/tfs/TB1CHN3u4z1gK0jSZSgXXavwpXa-112-64.png', + label: '普通型', + schema: { + componentName: 'Div', + props: {}, + }, + }, + ], + configure: [ + { + name: 'myName', + title: '我的名字', + display: 'tab', + initialValue: 'NORMAL', + defaultValue: 'NORMAL', + collapsed: true, + supportVariable: true, + accessor(field, val) {}, + mutator(field, val) {}, + disabled() { + return true; + }, + useVariableChange() {}, + allowTextInput: true, + liveTextEditing: true, + setter: [ + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + ], + }, + { + name: 'mySlotName', + slotName: 'mySlotName', + slotTitle: '我的 Slot 名字', + display: 'tab', + initialValue: 'NORMAL', + defaultValue: 'NORMAL', + collapsed: true, + supportVariable: true, + accessor(field, val) {}, + mutator(field, val) {}, + disabled() { + return true; + }, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: 'behavior', + title: '默认状态', + display: 'inline', + initialValue: 'NORMAL', + supportVariable: true, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: '__style__', + title: '样式设置', + display: 'accordion', + collapsed: false, + initialValue: {}, + tip: { + url: 'https://lark.alipay.com/legao/help/design-tool-style', + content: '点击 ? 查看样式设置器用法指南', + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + title: '高级', + display: 'accordion', + items: [ + { + name: 'fieldId', + title: '唯一标识', + display: 'block', + tip: + '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。', + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'useFieldIdAsDomId', + title: '将唯一标识用作 DOM ID', + display: 'block', + tip: + '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用', + initialValue: false, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + name: 'customClassName', + title: '自定义样式类', + display: 'block', + supportVariable: true, + initialValue: '', + setter: { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'events', + title: '动作设置', + tip: { + url: 'https://lark.alipay.com/legao/legao/events-call', + content: '点击 ? 查看如何设置组件的事件响应动作', + }, + display: 'accordion', + initialValue: { + 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, + }, + }, + { + name: 'onClick', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseEnter', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseLeave', + display: 'none', + initialValue: { + ignored: true, + }, + }, + ], + }, + ], +}; diff --git a/packages/editor-preset-vision/tests/fixtures/schema/form.ts b/packages/editor-preset-vision/tests/fixtures/schema/form.ts index 905b89561..5492a9ffb 100644 --- a/packages/editor-preset-vision/tests/fixtures/schema/form.ts +++ b/packages/editor-preset-vision/tests/fixtures/schema/form.ts @@ -89,7 +89,7 @@ export default { children: [ { componentName: 'Form', - id: 'node_k1ow3cbq', + id: 'form', props: { size: 'medium', labelAlign: 'top', diff --git a/packages/editor-preset-vision/tests/props-reducer/init-node.test.ts b/packages/editor-preset-vision/tests/props-reducer/init-node.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/editor-preset-vision/tests/vision-api/exchange.test.ts b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts new file mode 100644 index 000000000..972f49541 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts @@ -0,0 +1,18 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine from '../../src'; + +describe('VisualEngine.Exchange 相关 API 测试', () => { + it('select / getSelected', () => { + const doc = VisualEngine.Pages.addPage(formSchema); + VisualEngine.Exchange.select(doc?.getNode('form')); + expect(VisualEngine.Exchange.getSelected()?.componentName).toBe('Form'); + expect(VisualEngine.Exchange.getSelected()?.id).toBe('form'); + }); + + it('onIntoView', () => { + expect(typeof VisualEngine.Exchange.onIntoView).toBe('function'); + }); +}); diff --git a/packages/editor-preset-vision/tests/vision-api/pages.test.ts b/packages/editor-preset-vision/tests/vision-api/pages.test.ts index 259cbef63..a6567bd24 100644 --- a/packages/editor-preset-vision/tests/vision-api/pages.test.ts +++ b/packages/editor-preset-vision/tests/vision-api/pages.test.ts @@ -2,9 +2,10 @@ import set from 'lodash/set'; import cloneDeep from 'lodash/clonedeep'; import '../fixtures/window'; import formSchema from '../fixtures/schema/form'; -import VisualEngine from '../../src'; +import VisualEngine, { Prototype } from '../../src'; import { Editor } from '@ali/lowcode-editor-core'; import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; const pageSchema = { componentsTree: [formSchema] }; @@ -62,6 +63,14 @@ describe('VisualEngine.Pages 相关 API 测试', () => { // slot 会多出(1 + N)个节点 expect(doc.nodesMap.size).toBe(expectedNodeCnt + 2); }); + it.only('基本的节点模型初始化,初始化传入 schema,构造 prototype', () => { + const proto = new Prototype(divPrototypeConfig); + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + expect(doc.nodesMap.size).toBe(expectedNodeCnt); + }); it('导出 schema', () => { const doc = VisualEngine.Pages.addPage(pageSchema)!; expect(doc).toBeTruthy(); diff --git a/packages/editor-preset-vision/tests/vision-api/project.test.ts b/packages/editor-preset-vision/tests/vision-api/project.test.ts index 6e07cfb02..6bb7cc566 100644 --- a/packages/editor-preset-vision/tests/vision-api/project.test.ts +++ b/packages/editor-preset-vision/tests/vision-api/project.test.ts @@ -1,38 +1,26 @@ import set from 'lodash/set'; import cloneDeep from 'lodash/clonedeep'; import '../fixtures/window'; -// 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 VisualEngine from '../../src'; -import { Editor } from '@ali/lowcode-editor-core'; - -// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; - +import { Project } from '../../src'; describe.skip('VisualEngine.Project 相关 API 测试', () => { - describe('getSchema / setSchema 系列', () => { - it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { - // console.log(VisualEngine); - // console.log(Editor instanceof Function); - // console.log(Editor.toString()) - // console.log(new Editor()); - // console.log(Editor2 instanceof Function); - - console.log(VisualEngine.Pages.addPage(formSchema)); + it('getSchema / setSchema 系列', () => { + Project.setSchema({ + componentsMap: {}, + componentsTree: [formSchema], }); + expect(Project.getSchema()).toEqual({ + componentsMap: {}, + componentsTree: [formSchema], + }); + }); - describe('setConfig 系列', () => { - it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { - // console.log(VisualEngine); - // console.log(Editor instanceof Function); - // console.log(Editor.toString()) - // console.log(new Editor()); - // console.log(Editor2 instanceof Function); - - console.log(VisualEngine.Pages.addPage(formSchema)); + it('setConfig', () => { + Project.setConfig({ haha: 1 }); + expect(Project.get('config')).toEqual({ + haha: 1, }); }); -}); \ No newline at end of file +}); diff --git a/packages/editor-skeleton/.eslintrc.js b/packages/editor-skeleton/.eslintrc.js index 27e1e0d4f..c650ed99f 100644 --- a/packages/editor-skeleton/.eslintrc.js +++ b/packages/editor-skeleton/.eslintrc.js @@ -12,5 +12,6 @@ module.exports = { 'no-prototype-builtins': 1, 'no-confusing-arrow': 1, 'no-case-declarations': 1, + 'lines-between-class-members': 0, } } \ No newline at end of file diff --git a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx index 69ba92a2f..6527cc690 100644 --- a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx @@ -11,7 +11,7 @@ import { createIcon } from '@ali/lowcode-utils'; @observer export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any }, { shouldIgnoreRoot: boolean }> { state = { - shouldIgnoreRoot: false, + shouldIgnoreRoot: false, }; private main = new SettingsMain(this.props.editor); @@ -24,12 +24,12 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any componentDidMount() { this.setShouldIgnoreRoot(); } - + async setShouldIgnoreRoot() { - let designMode = await this.props.editor.get('designMode'); + const designMode = await this.props.editor.get('designMode'); this.setState({ shouldIgnoreRoot: designMode === 'live', - }) + }); } componentWillUnmount() {