From f68ad97cd5159e912481d8c3387820d8fa3f408b Mon Sep 17 00:00:00 2001 From: "lihao.ylh" Date: Thu, 24 Jun 2021 14:40:43 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=20prop=20/=20setting?= =?UTF-8?q?-prop-entry=20/=20setting-top-entry=20=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=9A=84=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 | 3 +- .../designer/setting/setting-prop-entry.ts | 8 +- packages/designer/src/document/node/node.ts | 2 +- .../designer/src/document/node/props/prop.ts | 22 +--- .../designer/src/document/node/props/props.ts | 3 +- .../tests/builtin-simulator/host.test.ts | 4 +- .../setting/setting-prop-entry.test.ts | 120 +++++++++++++++++- .../setting/setting-top-entry.test.ts | 26 ++++ .../tests/document/node/node.add.test.ts | 4 + .../tests/document/node/props/prop.test.ts | 115 ++++++++++++++--- .../tests/document/node/props/props.test.ts | 2 +- .../node/props/value-to-source.test.ts | 1 + .../designer/tests/project/project.test.ts | 8 ++ tsconfig.json | 2 +- 14 files changed, 269 insertions(+), 51 deletions(-) diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index 1a6063f1a..0c79d473b 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -6,7 +6,7 @@ module.exports = { // // '^.+\\.(ts|tsx)$': 'ts-jest', // // '^.+\\.(js|jsx)$': 'babel-jest', // }, - // testMatch: ['**/node.add.test.ts'], + // testMatch: ['**/setting-prop-entry.test.ts'], // testMatch: ['(/tests?/.*(test))\\.[jt]s$'], transformIgnorePatterns: [ `/node_modules/(?!${esModules})/`, @@ -22,6 +22,7 @@ module.exports = { '!src/builtin-simulator/utils/**', '!src/plugin/sequencify.ts', '!src/document/node/exclusive-group.ts', + '!src/document/node/props/value-to-source.ts', '!**/node_modules/**', '!**/vendor/**', ], diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts index 351af3cfe..5a8a369d1 100644 --- a/packages/designer/src/designer/setting/setting-prop-entry.ts +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -118,6 +118,7 @@ export class SettingPropEntry implements SettingEntry { * 1 类似值,比如数组长度一样 * 2 单一植 */ + /* istanbul ignore next */ @computed get valueState(): number { if (this.type !== 'field') { const { getValue } = this.extraProps; @@ -155,7 +156,6 @@ export class SettingPropEntry implements SettingEntry { try { return getValue ? getValue(this, val) : val; } catch (e) { - // todo: add log console.warn(e); return val; } @@ -175,7 +175,7 @@ export class SettingPropEntry implements SettingEntry { try { setValue(this, val); } catch (e) { - // todo: add log + /* istanbul ignore next */ console.warn(e); } } @@ -198,7 +198,7 @@ export class SettingPropEntry implements SettingEntry { try { setValue(this, undefined); } catch (e) { - // todo: add log + /* istanbul ignore next */ console.warn(e); } } @@ -320,7 +320,7 @@ export class SettingPropEntry implements SettingEntry { return; } const v = this.getValue(); - if (isJSExpression(v)) { + if (this.isUseVariable()) { this.setValue(v.mock); } else { this.setValue({ diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 9dfde4baf..0bc849f1a 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -1096,7 +1096,7 @@ export function isRootNode(node: Node): node is RootNode { } export function isLowCodeComponent(node: Node): boolean { - return node.componentMeta.getMetadata().devMode === 'lowcode'; + return node.componentMeta?.getMetadata().devMode === 'lowcode'; } export function getZLevelTop(child: Node, zLevel: number): Node | null { diff --git a/packages/designer/src/document/node/props/prop.ts b/packages/designer/src/document/node/props/prop.ts index 79a3564f9..f8237e8f0 100644 --- a/packages/designer/src/document/node/props/prop.ts +++ b/packages/designer/src/document/node/props/prop.ts @@ -98,7 +98,7 @@ export class Prop implements IPropParent { return this.export(TransformStage.Serilize); } - export(stage: TransformStage = TransformStage.Save): CompositeValue | UNSET { + export(stage: TransformStage = TransformStage.Save): CompositeValue { stage = compatStage(stage); const type = this._type; if (stage === TransformStage.Render && this.key === '___condition___') { @@ -110,7 +110,6 @@ export class Prop implements IPropParent { } if (type === 'unset') { - // return UNSET; @康为 之后 review 下这块改造 return undefined; } @@ -147,10 +146,6 @@ export class Prop implements IPropParent { const maps: any = {}; this.items!.forEach((prop, key) => { const v = prop.export(stage); - // if (v !== UNSET) { - // maps[prop.key == null ? key : prop.key] = v; - // } - // @康为 之后 review 下这块改造 maps[prop.key == null ? key : prop.key] = v; }); return maps; @@ -161,12 +156,9 @@ export class Prop implements IPropParent { return this._value; } return this.items!.map((prop) => { - const v = prop.export(stage); - return v === UNSET ? undefined : v; + return prop.export(stage); }); } - - return undefined; } private _code: string | null = null; @@ -246,7 +238,7 @@ export class Prop implements IPropParent { } else { this._type = 'map'; } - } else { + } /* istanbul ignore next */ else { this._type = 'expression'; this._value = { type: 'JSExpression', @@ -267,11 +259,7 @@ export class Prop implements IPropParent { } @computed getValue(): CompositeValue { - const v = this.export(TransformStage.Serilize); - if (v === UNSET) { - return undefined; - } - return v; + return this.export(TransformStage.Serilize); } private dispose() { @@ -563,7 +551,7 @@ export class Prop implements IPropParent { items.push(prop); maps.set(key, prop); } - } else { + } /* istanbul ignore next */ else { return null; } diff --git a/packages/designer/src/document/node/props/props.ts b/packages/designer/src/document/node/props/props.ts index 2d24e477d..1e900710f 100644 --- a/packages/designer/src/document/node/props/props.ts +++ b/packages/designer/src/document/node/props/props.ts @@ -161,8 +161,9 @@ export class Props implements IPropParent { /** * @deprecated */ + /* istanbul ignore next */ private transformToStatic(props: any) { - let transducers = this.owner.componentMeta.prototype?.options?.transducers; + let transducers = this.owner.componentMeta?.prototype?.options?.transducers; if (!transducers) { return props; } diff --git a/packages/designer/tests/builtin-simulator/host.test.ts b/packages/designer/tests/builtin-simulator/host.test.ts index 9b4121267..ab96b20ec 100644 --- a/packages/designer/tests/builtin-simulator/host.test.ts +++ b/packages/designer/tests/builtin-simulator/host.test.ts @@ -408,7 +408,7 @@ describe('Host 测试', () => { host.setupContextMenu(); host.getNodeInstanceFromElement = () => { return { - node: { componentMeta: { componentName: 'Button' } }, + node: { componentMeta: { componentName: 'Button', getMetadata() { return {} } } }, }; }; const mockFn = jest.fn(); @@ -428,7 +428,7 @@ describe('Host 测试', () => { dispatchEvent() {}, }; - // 非法分支测试 + // 非法分支测试 host.mountContentFrame(); expect(host._iframe).toBeUndefined(); diff --git a/packages/designer/tests/designer/setting/setting-prop-entry.test.ts b/packages/designer/tests/designer/setting/setting-prop-entry.test.ts index 655dea54c..e5bb5f6c5 100644 --- a/packages/designer/tests/designer/setting/setting-prop-entry.test.ts +++ b/packages/designer/tests/designer/setting/setting-prop-entry.test.ts @@ -1,8 +1,11 @@ +// @ts-nocheck 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 { SettingTopEntry } from '../../../src/designer/setting/setting-top-entry'; +import { SettingPropEntry } from '../../../src/designer/setting/setting-prop-entry'; import { Node } from '../../../src/document/node/node'; import { Designer } from '../../../src/designer/designer'; import formSchema from '../../../fixtures/schema/form'; @@ -28,6 +31,119 @@ describe('setting-prop-entry 测试', () => { doc = null; }); + describe('纯粹的 UnitTest', () => { + let mockNode: Node; + let mockTopEntry: SettingTopEntry; + beforeEach(() => { + mockNode = new Node(designer.currentDocument, { + componentName: 'Button', + props: { + a: 'str', + b: 222, + obj: { + x: 1, + }, + jse: { + type: 'JSExpression', + value: 'state.a', + mock: 111, + } + }, + }); + mockTopEntry = new SettingTopEntry(editor, [mockNode]); + }); + afterEach(() => { + mockNode = null; + mockTopEntry = null; + }); + + it('常规方法', () => { + // type: group 类型 + const prop = new SettingPropEntry(mockTopEntry, 'xGroup', 'group'); + expect(prop.setKey('xxx')).toBeUndefined(); + expect(prop.remove()).toBeUndefined(); + + const prop2 = new SettingPropEntry(mockTopEntry, '#xGroup'); + expect(prop2.setKey('xxx')).toBeUndefined(); + expect(prop2.remove()).toBeUndefined(); + + expect(prop.getVariableValue()).toBe(''); + }); + + it('setValue / getValue / onValueChange', () => { + // 获取已有的 prop + const prop1 = mockTopEntry.getProp('a'); + prop1.extraProps = { + getValue: (prop, val) => `prefix ${val}`, + setValue: (prop, val) => { prop.setValue(`modified ${val}`, false, false, { disableMutator: true }) }, + defaultValue: 'default', + }; + + expect(prop1.getDefaultValue()).toBe('default'); + expect(prop1.getValue()).toBe('prefix str'); + + // disableMutator: true + prop1.setValue('bbb', false, false, { disableMutator: true }); + expect(prop1.getValue()).toBe('prefix bbb'); + + // disableMutator: false + prop1.setValue('bbb'); + expect(prop1.getValue()).toBe('prefix modified bbb'); + + const mockFn3 = jest.fn(); + const prop2 = mockTopEntry.getProp('obj'); + const prop3 = prop2.get('x'); + const offFn = prop3.onValueChange(mockFn3); + expect(prop3.getValue()).toBe(1); + prop3.setValue(2); + expect(mockFn3).toHaveBeenCalled(); + + offFn(); + prop3.setValue(3); + mockFn3.mockClear(); + expect(mockFn3).toHaveBeenCalledTimes(0); + + const prop4 = mockTopEntry.getProp('b'); + prop4.extraProps = { + getValue: () => { throw 'error'; }, + }; + expect(prop4.getValue()).toBe(222); + }); + + it('clearValue', () => { + const prop1 = mockTopEntry.getProp('a'); + prop1.clearValue(); + expect(prop1.getValue()).toBeUndefined(); + + const mockFn = jest.fn(); + prop1.extraProps = { + setValue: mockFn, + }; + prop1.clearValue(); + expect(mockFn).toHaveBeenCalled(); + }); + + it('getVariableValue/ setUseVariable / isUseVariable / getMockOrValue', () => { + const prop1 = mockTopEntry.getProp('jse'); + + expect(prop1.isUseVariable()).toBeTruthy(); + expect(prop1.useVariable).toBeTruthy(); + + expect(prop1.getMockOrValue()).toEqual(111); + expect(prop1.getVariableValue()).toEqual('state.a'); + + prop1.setUseVariable(false); + expect(prop1.getValue()).toEqual(111); + prop1.setUseVariable(true); + expect(prop1.getValue()).toEqual({ + type: 'JSExpression', + value: '', + mock: 111, + }); + prop1.setUseVariable(true); + }); + }); + describe('node 构造函数生成 settingEntry', () => { it('常规方法测试', () => { const divNode = doc?.getNode('div'); @@ -81,9 +197,7 @@ describe('setting-prop-entry 测试', () => { expect(divNode?.getProp('behavior').getValue()).toBeUndefined(); }); - it.skip('type: group 场景测试', () => { - - }); + it.skip('type: group 场景测试', () => {}); it('JSExpression 类型的 prop', () => { designer.createComponentMeta(divMeta); diff --git a/packages/designer/tests/designer/setting/setting-top-entry.test.ts b/packages/designer/tests/designer/setting/setting-top-entry.test.ts index 94fdbab90..b0dc27012 100644 --- a/packages/designer/tests/designer/setting/setting-top-entry.test.ts +++ b/packages/designer/tests/designer/setting/setting-top-entry.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import set from 'lodash/set'; import cloneDeep from 'lodash/cloneDeep'; import '../../fixtures/window'; @@ -75,6 +76,31 @@ describe('setting-top-entry 测试', () => { settingEntry.getValue(); }); + it('onMetadataChange', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div') as Node; + + const { settingEntry } = divNode!; + const mockFn = jest.fn(); + settingEntry.componentMeta.onMetadataChange(mockFn); + settingEntry.componentMeta.refreshMetadata(); + expect(mockFn).toHaveBeenCalled(); + }); + + it.skip('setupItems - customView', () => { + designer.createComponentMeta(divMeta); + designer.project.open(settingSchema); + const { currentDocument } = designer.project; + const divNode = currentDocument?.getNode('div') as Node; + + const { settingEntry } = divNode; + // 模拟将第一个配置变成 react funcional component + settingEntry.componentMeta.getMetadata().combined[0].items[0] = props => props.xx; + settingEntry.setupItems(); + }); + it('清理方法测试', () => { designer.createComponentMeta(divMeta); designer.project.open(settingSchema); diff --git a/packages/designer/tests/document/node/node.add.test.ts b/packages/designer/tests/document/node/node.add.test.ts index 200fe524f..c9dd53d1f 100644 --- a/packages/designer/tests/document/node/node.add.test.ts +++ b/packages/designer/tests/document/node/node.add.test.ts @@ -64,6 +64,10 @@ describe('schema 生成节点模型测试', () => { const exportSchema = currentDocument?.export(1); expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt); + nodesMap.forEach(node => { + // 触发 getter + node.settingEntry; + }); expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt); }); diff --git a/packages/designer/tests/document/node/props/prop.test.ts b/packages/designer/tests/document/node/props/prop.test.ts index 00ab084e5..3c01de7ec 100644 --- a/packages/designer/tests/document/node/props/prop.test.ts +++ b/packages/designer/tests/document/node/props/prop.test.ts @@ -1,16 +1,32 @@ +// @ts-nocheck import '../../../fixtures/window'; -import { set, delayObxTick } from '../../../utils'; -import { Editor } from '@ali/lowcode-editor-core'; -import { Props } from '../../../../src/document/node/props/props'; +import { delayObxTick } from '../../../utils'; +import { Editor, engineConfig } from '@ali/lowcode-editor-core'; import { Designer } from '../../../../src/designer/designer'; -import { Project } from '../../../../src/project/project'; import { DocumentModel } from '../../../../src/document/document-model'; import { Prop, isProp, isValidArrayIndex } from '../../../../src/document/node/props/prop'; import { TransformStage } from '@ali/lowcode-types'; - +const slotNodeImportMockFn = jest.fn(); +const slotNodeRemoveMockFn = jest.fn(); const mockedOwner = { componentName: 'Div', + addSlot() {}, + document: { + createNode(schema) { + return { + ...schema, + addSlot() {}, + internalSetSlotFor() {}, + import: slotNodeImportMockFn, + export() { + return schema; + }, + remove: slotNodeRemoveMockFn, + }; + }, + designer: {}, + }, }; const mockedPropsInst = { @@ -32,7 +48,19 @@ describe('Prop 类测试', () => { numProp = new Prop(mockedPropsInst, 1, 'numProp'); nullProp = new Prop(mockedPropsInst, null, 'nullProp'); expProp = new Prop(mockedPropsInst, { type: 'JSExpression', value: 'state.haha' }, 'expProp'); - // slotProp = new Prop(mockedPropsInst, { type: 'JSSlot', value: [{ componentName: 'Button' }] }, 'slotProp'); + slotProp = new Prop( + mockedPropsInst, + { + type: 'JSSlot', + title: '测试 slot', + name: 'testSlot', + params: { a: 1 }, + value: [{ componentName: 'Button' }], + }, + 'slotProp', + ); + slotNodeImportMockFn.mockClear(); + slotNodeRemoveMockFn.mockClear(); }); afterEach(() => { boolProp.purge(); @@ -40,7 +68,7 @@ describe('Prop 类测试', () => { numProp.purge(); nullProp.purge(); expProp.purge(); - // slotProp.purge(); + slotProp.purge(); }); it('consturctor / getProps / getNode', () => { @@ -60,6 +88,7 @@ describe('Prop 类测试', () => { expect(numProp.set()).toBeNull(); expect(numProp.has()).toBeFalsy(); + expect(numProp.path).toEqual(['numProp']); }); it('getValue / getAsString / setValue', () => { @@ -121,7 +150,28 @@ describe('Prop 类测试', () => { expect( new Prop(mockedPropsInst, false, '___condition___').export(TransformStage.Render), ).toBeTruthy(); - // console.log(slotProp.export(TransformStage.Render)); + engineConfig.set('enableCondition', true); + expect( + new Prop(mockedPropsInst, false, '___condition___').export(TransformStage.Render), + ).toBeFalsy(); + expect(slotProp.export(TransformStage.Render)).toEqual({ + type: 'JSSlot', + params: { a: 1 }, + value: { + componentName: 'Slot', + title: '测试 slot', + name: 'testSlot', + params: { a: 1 }, + children: [{ componentName: 'Button' }], + }, + }); + expect(slotProp.export(TransformStage.Save)).toEqual({ + type: 'JSSlot', + params: { a: 1 }, + value: [{ componentName: 'Button' }], + title: '测试 slot', + name: 'testSlot', + }); }); it('compare', () => { @@ -145,6 +195,21 @@ describe('Prop 类测试', () => { boolProp.purge(); }); + it('slot', () => { + // 更新 slot + slotProp.setValue({ + type: 'JSSlot', + value: [{ + componentName: 'Form', + }] + }); + expect(slotNodeImportMockFn).toBeCalled(); + + // 节点类型转换 + slotProp.setValue(true); + expect(slotNodeRemoveMockFn).toBeCalled(); + }); + it('迭代器 / map / forEach', () => { const mockedFn = jest.fn(); for (const item of strProp) { @@ -153,13 +218,13 @@ describe('Prop 类测试', () => { expect(mockedFn).not.toHaveBeenCalled(); mockedFn.mockClear(); - strProp.forEach(item => { + strProp.forEach((item) => { mockedFn(); }); expect(mockedFn).not.toHaveBeenCalled(); mockedFn.mockClear(); - strProp.map(item => { + strProp.map((item) => { return mockedFn(); }); expect(mockedFn).not.toHaveBeenCalled(); @@ -201,7 +266,6 @@ describe('Prop 类测试', () => { z2: 'str', }); - expect(prop.getPropValue('a')).toBe(1); prop.setPropValue('a', 2); expect(prop.getPropValue('a')).toBe(2); @@ -283,13 +347,13 @@ describe('Prop 类测试', () => { expect(mockedFn).toHaveBeenCalledTimes(5); mockedFn.mockClear(); - prop.forEach(item => { + prop.forEach((item) => { mockedFn(); }); expect(mockedFn).toHaveBeenCalledTimes(5); mockedFn.mockClear(); - prop.map(item => { + prop.map((item) => { return mockedFn(); }); expect(mockedFn).toHaveBeenCalledTimes(5); @@ -297,6 +361,7 @@ describe('Prop 类测试', () => { }); it('dispose', () => { + prop.items; prop.dispose(); expect(prop._items).toBeNull(); @@ -321,9 +386,15 @@ describe('Prop 类测试', () => { expect(prop.get(2).getValue()).toBe('haha'); expect(prop.getAsString()).toBe(''); + + prop.unset(); + prop.set(0, true); + expect(prop.set('x', 'invalid')).toBeNull(); + expect(prop.get(0).getValue()).toBeTruthy(); }); it('export', () => { + expect(prop.export()).toEqual([1, true, 'haha']); // 触发构造 prop.items; expect(prop.export()).toEqual([1, true, 'haha']); @@ -351,18 +422,22 @@ describe('Prop 类测试', () => { const designer = new Designer({ editor }); const doc = new DocumentModel(designer.project, { componentName: 'Page', - children: [{ - id: 'div', - componentName: 'Div', - }], + children: [ + { + id: 'div', + componentName: 'Div', + }, + ], }); const div = doc.getNode('div'); const slotProp = new Prop(div?.getProps(), { type: 'JSSlot', - value: [{ - componentName: 'Button', - }], + value: [ + { + componentName: 'Button', + }, + ], }); expect(slotProp.slotNode?.componentName).toBe('Slot'); diff --git a/packages/designer/tests/document/node/props/props.test.ts b/packages/designer/tests/document/node/props/props.test.ts index 56597ecc2..eb49568a5 100644 --- a/packages/designer/tests/document/node/props/props.test.ts +++ b/packages/designer/tests/document/node/props/props.test.ts @@ -1,4 +1,4 @@ - +// @ts-nocheck import '../../../fixtures/window'; import { set, delayObxTick } from '../../../utils'; import { Editor } from '@ali/lowcode-editor-core'; diff --git a/packages/designer/tests/document/node/props/value-to-source.test.ts b/packages/designer/tests/document/node/props/value-to-source.test.ts index 2694211e8..43ed19b2a 100644 --- a/packages/designer/tests/document/node/props/value-to-source.test.ts +++ b/packages/designer/tests/document/node/props/value-to-source.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import '../../../fixtures/silent-console'; import { getSource, valueToSource } from '../../../../src/document/node/props/value-to-source'; diff --git a/packages/designer/tests/project/project.test.ts b/packages/designer/tests/project/project.test.ts index 1fa17f8e4..6f2ddddb5 100644 --- a/packages/designer/tests/project/project.test.ts +++ b/packages/designer/tests/project/project.test.ts @@ -59,6 +59,10 @@ describe('schema 生成节点模型测试', () => { const exportSchema = currentDocument?.export(1); expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt); + nodesMap.forEach(node => { + // 触发 getter + node.settingEntry; + }); expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt); }); @@ -77,6 +81,10 @@ describe('schema 生成节点模型测试', () => { const exportSchema = currentDocument?.export(1); expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt); + nodesMap.forEach(node => { + // 触发 getter + node.settingEntry; + }); expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt); }); diff --git a/tsconfig.json b/tsconfig.json index 556a1fea8..fce8ef03d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,5 +37,5 @@ }, "outDir": "lib" }, - "exclude": ["**/test", "**/lib", "**/es", "node_modules"] + "exclude": ["**/tests/*", "**/*.test.ts", "**/lib", "**/es", "node_modules"] }