chore(test): 补充 node-children / modal-nodes-manager 单测

This commit is contained in:
力皓 2020-12-11 15:29:49 +08:00
parent d48e6ae0c8
commit 94fb4d2598
8 changed files with 1948 additions and 13 deletions

View File

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

View File

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

View File

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

View File

@ -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', () => {

View File

@ -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: [],
},
};

View File

@ -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: [],
},
};

File diff suppressed because it is too large Load Diff

View File

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