test: 增加 prop / setting-prop-entry / setting-top-entry 部分的单测

This commit is contained in:
lihao.ylh 2021-06-24 14:40:43 +08:00
parent 97099d0d0b
commit f68ad97cd5
14 changed files with 269 additions and 51 deletions

View File

@ -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/**',
],

View File

@ -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({

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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);
});

View File

@ -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: [{
children: [
{
id: 'div',
componentName: 'Div',
}],
},
],
});
const div = doc.getNode('div');
const slotProp = new Prop(div?.getProps(), {
type: 'JSSlot',
value: [{
value: [
{
componentName: 'Button',
}],
},
],
});
expect(slotProp.slotNode?.componentName).toBe('Slot');

View File

@ -1,4 +1,4 @@
// @ts-nocheck
import '../../../fixtures/window';
import { set, delayObxTick } from '../../../utils';
import { Editor } from '@ali/lowcode-editor-core';

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import '../../../fixtures/silent-console';
import { getSource, valueToSource } from '../../../../src/document/node/props/value-to-source';

View File

@ -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);
});

View File

@ -37,5 +37,5 @@
},
"outDir": "lib"
},
"exclude": ["**/test", "**/lib", "**/es", "node_modules"]
"exclude": ["**/tests/*", "**/*.test.ts", "**/lib", "**/es", "node_modules"]
}