refactor: 调整 node.prop.change 事件的发起位置

refactor: 支持部分老的 vision API
chore: typo fix currentDocument
This commit is contained in:
力皓 2020-12-01 20:39:09 +08:00
parent 3ee5fba476
commit d811850a51
33 changed files with 1690 additions and 190 deletions

View File

@ -70,6 +70,10 @@ export class ComponentMeta {
return this._npm;
}
set npm(_npm) {
this._npm = _npm;
}
private _componentName?: string;
get componentName(): string {

View File

@ -166,6 +166,8 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
this.setValue(v, false, false, options);
}
this.notifyValueChange();
// dirty fix list setter
if (Array.isArray(data) && data[0] && data[0].__sid__) {
return;

View File

@ -276,6 +276,10 @@ export class SettingPropEntry implements SettingEntry {
this.emitter.emit('valuechange');
}
notifyValueChange() {
this.editor.emit('node.prop.change', { node: this.getNode(), prop: this });
}
getDefaultValue() {
return this.extraProps.defaultValue;
}

View File

@ -1,4 +1,4 @@
import { untracked, computed, obx, globalContext, Editor } from '@ali/lowcode-editor-core';
import { untracked, computed, obx } from '@ali/lowcode-editor-core';
import { CompositeValue, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
import { uniqueId, isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
import { PropStash } from './prop-stash';
@ -247,9 +247,6 @@ export class Prop implements IPropParent {
value: valueToSource(val),
};
}
if (globalContext.has(Editor)) {
globalContext.get(Editor).emit('node.prop.change', { prop: this, node: this.owner });
}
this.dispose();
}

View File

@ -1,147 +0,0 @@
import domReady from 'domready';
import { EventEmitter } from 'events';
const Shells = ['iphone6'];
export class Flags {
public emitter: EventEmitter;
public flags: string[];
public ready: boolean;
public lastFlags: string[];
public lastShell: string;
private lastSimulatorDevice: string;
constructor() {
this.emitter = new EventEmitter();
this.flags = ['design-mode'];
domReady(() => {
this.ready = true;
this.applyFlags();
});
}
public setDragMode(flag: boolean) {
if (flag) {
this.add('drag-mode');
} else {
this.remove('drag-mode');
}
}
public setPreviewMode(flag: boolean) {
if (flag) {
this.add('preview-mode');
this.remove('design-mode');
} else {
this.add('design-mode');
this.remove('preview-mode');
}
}
public setWithShell(shell: string) {
if (shell === this.lastShell) {
return;
}
if (this.lastShell) {
this.remove(`with-${this.lastShell}shell`);
}
if (shell) {
if (Shells.indexOf(shell) < 0) {
shell = Shells[0];
}
this.add(`with-${shell}shell`);
this.lastShell = shell;
}
}
public setSimulator(device: string) {
if (this.lastSimulatorDevice) {
this.remove(`simulator-${this.lastSimulatorDevice}`);
}
if (device !== '' && device !== 'pc') {
this.add(`simulator-${device}`);
}
this.lastSimulatorDevice = device;
}
public setHideSlate(flag: boolean) {
if (this.has('slate-fixed')) {
return;
}
if (flag) {
this.add('hide-slate');
} else {
this.remove('hide-slate');
}
}
public setSlateFixedMode(flag: boolean) {
if (flag) {
this.remove('hide-slate');
this.add('slate-fixed');
} else {
this.remove('slate-fixed');
}
}
public setSlateFullMode(flag: boolean) {
if (flag) {
this.add('slate-full-screen');
} else {
this.remove('slate-full-screen');
}
}
public getFlags() {
return this.flags;
}
public applyFlags(modifiedFlag?: string) {
if (!this.ready) {
return;
}
const doe = document.documentElement;
if (this.lastFlags) {
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
doe.classList.remove(`engine-${flag}`);
});
}
this.flags.forEach((flag) => {
doe.classList.add(`engine-${flag}`);
});
this.lastFlags = this.flags.slice(0);
this.emitter.emit('flagschange', this.flags, modifiedFlag);
}
public has(flag: string) {
return this.flags.indexOf(flag) > -1;
}
public add(flag: string) {
if (!this.has(flag)) {
this.flags.push(flag);
this.applyFlags(flag);
}
}
public remove(flag: string) {
const i = this.flags.indexOf(flag);
if (i > -1) {
this.flags.splice(i, 1);
this.applyFlags(flag);
}
}
public onFlagsChange(func: () => any) {
this.emitter.on('flagschange', func);
return () => {
this.emitter.removeListener('flagschange', func);
};
}
}
export default new Flags();

View File

@ -228,6 +228,14 @@ class Prototype {
return this.meta.npm?.package;
}
set packageName(pkgName) {
if (this.meta.npm) {
this.meta.npm.package = pkgName;
} else {
this.meta.npm = { package: pkgName };
}
}
// 兼容原 vision 用法
view: ComponentType | undefined;

View File

@ -30,7 +30,7 @@ export class Trunk {
}
getList(): any[] {
const list = this.trunk.reduceRight((prev, cur) => prev.concat(cur.getList()), []);
const list = this.trunk.filter(o => o).reduceRight((prev, cur) => prev.concat(cur.getList()), []);
const result: Prototype[] = [];
list.forEach((item: Prototype) => {
if (!result.find(r => r.options.componentName === item.options.componentName)) {

View File

@ -37,7 +37,7 @@ editor.set('designer', designer);
designer.project.onCurrentDocumentChange((doc) => {
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
editor.set('currentDocuemnt', doc);
editor.set('currentDocument', doc);
});
// 升级 Props

View File

@ -108,14 +108,14 @@ export class Flags {
return;
}
const doe = document.documentElement;
const doc = document.documentElement;
if (this.lastFlags) {
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
doe.classList.remove(`engine-${flag}`);
doc.classList.remove(`engine-${flag}`);
});
}
this.flags.forEach((flag) => {
doe.classList.add(`engine-${flag}`);
doc.classList.add(`engine-${flag}`);
});
this.lastFlags = this.flags.slice(0);

View File

@ -47,6 +47,7 @@ const pages = Object.assign(project, {
if (isPageDataV1(pages[0])) {
componentsTree = [pages[0].layout];
} else {
// if (!pages[0].componentsTree) return;
componentsTree = pages[0].componentsTree;
if (componentsTree[0]) {
componentsTree[0].componentName = componentsTree[0].componentName || 'Page';

View File

@ -66,6 +66,7 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
contentProps: props,
index: index || props?.index,
};
if (type === 'dock') {
newConfig.type = 'PanelDock';
newConfig.area = 'left';
@ -98,8 +99,6 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
height,
maxHeight,
canSetFixed,
onInit: init,
onDestroy: destroy,
};
if (defaultFixed) {
@ -121,23 +120,21 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
});
}
}
} else if (type === 'action') {
newConfig.area = 'top';
newConfig.type = 'Dock';
} else if (type === 'tab') {
newConfig.area = 'right';
newConfig.type = 'Panel';
} else if (type === 'stage') {
newConfig.area = 'stages';
newConfig.type = 'Widget';
} else {
newConfig.props.onInit = init;
newConfig.props.onDestroy = destroy;
if (type === 'action') {
newConfig.area = 'top';
newConfig.type = 'Dock';
} else if (type === 'tab') {
newConfig.area = 'right';
newConfig.type = 'Panel';
} else if (type === 'stage') {
newConfig.area = 'stages';
newConfig.type = 'Widget';
} else {
newConfig.area = 'main';
newConfig.type = 'Widget';
}
newConfig.area = 'main';
newConfig.type = 'Widget';
}
newConfig.props.onInit = init;
newConfig.props.onDestroy = destroy;
return newConfig;
}

View File

@ -43,9 +43,8 @@ function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: st
s.setAttribute('type', 'text/css');
s.setAttribute('id', cssId);
doc.getElementsByTagName('head')[0].appendChild(s);
s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => {
return `${b / 2}px`;
}).replace(/:root/g, `.${ cssClass}`)));
}).replace(/:root/g, `.${cssClass}`)));
}
}

View File

@ -58,7 +58,6 @@ class StyleResource {
this.inited = true;
const { type, content } = this.config;
let styleElement: any;
if (type === 'URL') {
styleElement = document.createElement('link');
@ -195,7 +194,7 @@ export class Viewport {
async setDevice(device = 'pc') {
if (this.getDevice() !== device) {
this.device = device;
const currentDocument = await editor.onceGot('currentDocuemnt');
const currentDocument = await editor.onceGot('currentDocument');
currentDocument?.simulator?.set('device', device === 'mobile' ? 'mobile' : 'default');
// Flags.setSimulator(device);
// this.applyMediaCSS();

View File

@ -2,4 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
React.PropTypes = PropTypes;
window.React = React;
window.React = React;
document.documentElement.requestFullscreen = () => {};
document.exitFullscreen = () => {};

View File

@ -1,10 +1,4 @@
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 bus from '../../src/bus';
import { editor } from '../../src/editor';

View File

@ -0,0 +1,62 @@
import '../fixtures/window';
// import { Project } from '../../src/project/project';
// import { Node } from '../../src/document/node/node';
// import { Designer } from '../../src/designer/designer';
import { VisualEngineContext } from '../../src/context';
import { autorun } from '@ali/lowcode-editor-core';
describe('VisualEngineContext 测试', () => {
it('registerManager | getManager', () => {
const ctx = new VisualEngineContext();
ctx.registerManager({
mgr1: {},
});
ctx.registerManager('mgr2', {});
expect(ctx.getManager('mgr1')).toEqual({});
});
it('registerModule | getModule', () => {
const ctx = new VisualEngineContext();
ctx.registerModule({
mod1: {},
});
ctx.registerModule('mod2', {});
expect(ctx.getModule('mod1')).toEqual({});
});
it('use | getPlugin', () => {
const ctx = new VisualEngineContext();
ctx.use('plugin1', { plugin: 1 });
ctx.registerManager({
mgr1: { manager: 1 },
});
ctx.registerModule({
mod1: { mod: 1 },
});
expect(ctx.getPlugin('plugin1')).toEqual({ plugin: 1 });
expect(ctx.getPlugin('mgr1')).toEqual({ manager: 1 });
expect(ctx.getPlugin('mod1')).toEqual({ mod: 1 });
expect(ctx.getPlugin()).toBeUndefined;
ctx.use('ve.settingField.variableSetter', {});
});
it('registerTreePane | getModule', () => {
const ctx = new VisualEngineContext();
ctx.registerTreePane({ pane: 1 }, { core: 2 });
expect(ctx.getModule('TreePane')).toEqual({ pane: 1 });
expect(ctx.getModule('TreeCore')).toEqual({ core: 2 });
});
it('registerDynamicSetterProvider', () => {
const ctx = new VisualEngineContext();
ctx.registerDynamicSetterProvider({});
expect(ctx.getPlugin('ve.plugin.setterProvider')).toEqual({});
ctx.registerDynamicSetterProvider();
});
});

View File

@ -0,0 +1,182 @@
import '../fixtures/window';
// import { Project } from '../../src/project/project';
// import { Node } from '../../src/document/node/node';
// import { Editor } from '@ali/lowcode-editor-core';
// import { Designer } from '@ali/lowcode-designer';
import { designer } from '../../src/editor';
import DragEngine from '../../src/drag-engine';
import formSchema from '../fixtures/schema/form';
// const editor = new Editor();
// const designer = new Designer({ editor });
designer.project.open(formSchema);
const mockBoostPrototype = jest.fn((e: MouseEvent) => {
return {
isPrototype: true,
getComponentName() {
return 'Div';
},
};
});
const mockBoostNode = jest.fn((e: MouseEvent) => {
return designer.currentDocument?.getNode('node_k1ow3cbo');
});
const mockBoostNodeData = jest.fn((e: MouseEvent) => {
return {
type: 'NodeData',
componentName: 'Div',
};
});
const mockBoostNull = jest.fn((e: MouseEvent) => {
return null;
});
const mockDragstart = jest.fn();
const mockDrag = jest.fn();
const mockDragend = jest.fn();
describe('drag-engine 测试', () => {
it('prototype', async () => {
DragEngine.from(document, mockBoostPrototype);
DragEngine.onDragstart(mockDragstart);
DragEngine.onDrag(mockDrag);
DragEngine.onDragend(mockDragend);
const mousedownEvt = new MouseEvent('mousedown');
document.dispatchEvent(mousedownEvt);
designer.dragon.emitter.emit('dragstart', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
},
originalEvent: mousedownEvt,
});
// await new Promise(resolve => resolve(setTimeout, 500));
expect(mockDragstart).toHaveBeenCalled();
designer.dragon.emitter.emit('drag', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
},
originalEvent: mousedownEvt,
});
expect(mockDrag).toHaveBeenCalled();
expect(DragEngine.inDragging()).toBeTruthy;
designer.dragon.emitter.emit('dragend', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
},
originalEvent: mousedownEvt,
});
expect(mockDragend).toHaveBeenCalled();
});
it('Node', async () => {
DragEngine.from(document, mockBoostNode);
DragEngine.onDragstart(mockDragstart);
DragEngine.onDrag(mockDrag);
DragEngine.onDragend(mockDragend);
const mousedownEvt = new MouseEvent('mousedown');
document.dispatchEvent(mousedownEvt);
designer.dragon.emitter.emit('dragstart', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
},
originalEvent: mousedownEvt,
});
// await new Promise(resolve => resolve(setTimeout, 500));
expect(mockDragstart).toHaveBeenCalled();
designer.dragon.emitter.emit('drag', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
},
originalEvent: mousedownEvt,
});
expect(mockDrag).toHaveBeenCalled();
designer.dragon.emitter.emit('dragend', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
},
originalEvent: mousedownEvt,
});
expect(mockDragend).toHaveBeenCalled();
});
it('NodeData', async () => {
DragEngine.from(document, mockBoostNodeData);
DragEngine.onDragstart(mockDragstart);
DragEngine.onDrag(mockDrag);
DragEngine.onDragend(mockDragend);
const mousedownEvt = new MouseEvent('mousedown');
document.dispatchEvent(mousedownEvt);
designer.dragon.emitter.emit('dragstart', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
},
originalEvent: mousedownEvt,
});
// await new Promise(resolve => resolve(setTimeout, 500));
expect(mockDragstart).toHaveBeenCalled();
designer.dragon.emitter.emit('drag', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')]
},
originalEvent: mousedownEvt,
});
expect(mockDrag).toHaveBeenCalled();
designer.dragon.emitter.emit('dragend', {
dragObject: {
type: 'nodedata',
data: {
componentName: 'Div',
},
},
originalEvent: mousedownEvt,
});
expect(mockDragend).toHaveBeenCalled();
});
it('null', async () => {
DragEngine.from(document, mockBoostNull);
DragEngine.onDragstart(mockDragstart);
DragEngine.onDrag(mockDrag);
DragEngine.onDragend(mockDragend);
const mousedownEvt = new MouseEvent('mousedown');
document.dispatchEvent(mousedownEvt);
designer.dragon.emitter.emit('dragstart', {
dragObject: {
nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')],
},
originalEvent: mousedownEvt,
});
expect(mockDragstart).toHaveBeenCalled();
});
});

View File

@ -1,11 +1,8 @@
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 env from '../../src/env';
import bus from 'editor-preset-vision/src/bus';
import { autorun } from '@ali/lowcode-editor-core';
describe('env 测试', () => {

View File

@ -0,0 +1,71 @@
import '../fixtures/window';
import flagsCtrl from '../../src/flags';
import domready from 'domready';
jest.mock('domready', () => {
return (fn) => fn();
});
// domready.mockImplementation((fn) => fn());
describe('flags 测试', () => {
it('flags', () => {
const mockFlagsChange = jest.fn();
flagsCtrl.flags = [];
const off = flagsCtrl.onFlagsChange(mockFlagsChange);
flagsCtrl.add('a');
expect(mockFlagsChange).toHaveBeenCalledTimes(1);
off();
flagsCtrl.add('b');
expect(mockFlagsChange).toHaveBeenCalledTimes(1);
expect(flagsCtrl.getFlags()).toEqual(['a', 'b']);
flagsCtrl.flags = [];
flagsCtrl.setDragMode(true);
expect(flagsCtrl.getFlags()).toEqual(['drag-mode']);
flagsCtrl.setDragMode(false);
expect(flagsCtrl.getFlags()).toEqual([]);
flagsCtrl.setPreviewMode(true);
expect(flagsCtrl.getFlags()).toEqual(['preview-mode']);
flagsCtrl.setPreviewMode(false);
expect(flagsCtrl.getFlags()).toEqual(['design-mode']);
flagsCtrl.flags = [];
flagsCtrl.setHideSlate(true);
expect(flagsCtrl.getFlags()).toEqual(['hide-slate']);
flagsCtrl.setHideSlate(false);
expect(flagsCtrl.getFlags()).toEqual([]);
flagsCtrl.flags = [];
flagsCtrl.setSlateFixedMode(true);
expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']);
flagsCtrl.setHideSlate(true);
expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']);
flagsCtrl.setSlateFixedMode(false);
expect(flagsCtrl.getFlags()).toEqual([]);
flagsCtrl.flags = [];
flagsCtrl.setSlateFullMode(true);
expect(flagsCtrl.getFlags()).toEqual(['slate-full-screen']);
flagsCtrl.setSlateFullMode(false);
expect(flagsCtrl.getFlags()).toEqual([]);
expect([].slice.apply(document.documentElement.classList)).toEqual(flagsCtrl.getFlags());
flagsCtrl.flags = [];
// setWithShell
flagsCtrl.setWithShell('shellA');
expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']);
flagsCtrl.setWithShell('iPhone6');
expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']);
flagsCtrl.flags = [];
// setSimulator
flagsCtrl.setSimulator('simA');
expect(flagsCtrl.getFlags()).toEqual(['simulator-simA']);
flagsCtrl.setSimulator('simB');
expect(flagsCtrl.getFlags()).toEqual(['simulator-simB']);
});
});

View File

@ -0,0 +1,176 @@
import '../fixtures/window';
// import { Project } from '../../src/project/project';
// import { Node } from '../../src/document/node/node';
// import { Designer } from '../../src/designer/designer';
import panes from '../../src/panes';
import { autorun } from '@ali/lowcode-editor-core';
describe('panes 测试', () => {
it('add: type dock | PanelDock', () => {
const mockDockShow = jest.fn();
const mockDockHide = jest.fn();
const { DockPane } = panes;
const offDockShow = DockPane.onDockShow(mockDockShow);
const offDockHide = DockPane.onDockHide(mockDockHide);
const pane1 = panes.add({
name: 'trunk',
type: 'dock',
width: 300,
description: '组件库',
contents: [
{
title: '普通组件',
tip: '普通组件',
content: () => 'haha',
},
],
menu: '组件库',
defaultFixed: true,
});
const pane2 = panes.add({
name: 'trunk2',
type: 'dock',
width: 300,
description: '组件库',
contents: [
{
title: '普通组件',
tip: '普通组件',
content: () => 'haha',
},
],
menu: '组件库',
defaultFixed: true,
});
const pane3 = panes.add({
name: 'trunk3',
type: 'dock',
isAction: true,
});
// DockPane.container.items.map(item => console.log(item.name))
// 2 trunks + 1 outline-pane
expect(DockPane.container.items.length).toBe(4);
DockPane.activeDock(pane1);
// expect(mockDockShow).toHaveBeenCalledTimes(1);
// expect(mockDockShow).toHaveBeenLastCalledWith(pane1);
expect(DockPane.container.items[1].visible).toBeTruthy;
DockPane.activeDock(pane2);
expect(DockPane.container.items[2].visible).toBeTruthy;
// expect(mockDockShow).toHaveBeenCalledTimes(2);
// expect(mockDockShow).toHaveBeenLastCalledWith(pane2);
// expect(mockDockHide).toHaveBeenCalledTimes(1);
// expect(mockDockHide).toHaveBeenLastCalledWith(pane1);
DockPane.activeDock();
DockPane.activeDock({ name: 'unexisting' });
offDockShow();
offDockHide();
// DockPane.activeDock(pane1);
// expect(mockDockShow).toHaveBeenCalledTimes(2);
// expect(mockDockHide).toHaveBeenCalledTimes(1);
expect(typeof DockPane.getDocks).toBe('function');
DockPane.getDocks();
expect(typeof DockPane.setFixed).toBe('function');
DockPane.setFixed();
});
it('add: type action', () => {
panes.add({
name: 'trunk',
type: 'action',
init() {},
destroy() {},
});
const { ActionPane } = panes;
expect(typeof ActionPane.getActions).toBe('function');
ActionPane.getActions();
expect(typeof ActionPane.setActions).toBe('function');
ActionPane.setActions();
expect(ActionPane.getActions()).toBe(ActionPane.actions);
});
it('add: type action - extraConfig', () => {
panes.add({
name: 'trunk',
type: 'action',
init() {},
destroy() {},
}, {});
});
it('add: type action - function', () => {
panes.add(() => ({
name: 'trunk',
type: 'action',
init() {},
destroy() {},
}));
});
it('add: type tab', () => {
panes.add({
name: 'trunk',
type: 'tab',
});
const { TabPane } = panes;
expect(typeof TabPane.setFloat).toBe('function');
TabPane.setFloat();
});
it('add: type stage', () => {
panes.add({
id: 'stage1',
type: 'stage',
});
panes.add({
type: 'stage',
});
const { Stages } = panes;
expect(typeof Stages.getStage).toBe('function');
Stages.getStage();
expect(typeof Stages.createStage).toBe('function');
Stages.createStage({
id: 'stage1',
type: 'stage',
});
Stages.createStage({
type: 'stage',
});
});
it('add: type stage - id', () => {
panes.add({
id: 'trunk',
name: 'trunk',
type: 'stage',
});
});
it('add: type widget', () => {
panes.add({
name: 'trunk',
type: 'widget',
});
});
it('add: type null', () => {
panes.add({
name: 'trunk',
});
const { toolbar } = panes;
expect(typeof toolbar.setContents).toBe('function');
toolbar.setContents();
});
});

View File

@ -0,0 +1,189 @@
import '../fixtures/window';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { editor } from '../../src/editor';
import { Viewport } from '../../src/viewport';
import domready from 'domready';
// const editor = globalContext.get(Editor);
jest.mock('domready', () => {
return (fn) => fn();
});
// 貌似 jsdom 没有响应 fullscreen 变更事件,先这么 mock 吧
const mockSetFullscreen = flag => { document.fullscreen = flag; };
describe('viewport 测试', () => {
mockSetFullscreen(true);
it('getDevice / setDevice / getViewport / onDeviceChange / onViewportChange', async () => {
const viewport = new Viewport();
const mockDeviceChange = jest.fn();
const mockViewportChange = jest.fn();
const offDevice = viewport.onDeviceChange(mockDeviceChange);
const offViewport = viewport.onViewportChange(mockViewportChange);
expect(viewport.getDevice()).toBe('pc');
expect(viewport.getViewport()).toBe('design-pc');
editor.set('currentDocument', { simulator: { set() {} } });
await viewport.setDevice('mobile');
expect(viewport.getDevice()).toBe('mobile');
expect(viewport.getViewport()).toBe('design-mobile');
expect(mockDeviceChange).toHaveBeenCalledTimes(1);
expect(mockViewportChange).toHaveBeenCalledTimes(1);
offDevice();
offViewport();
await viewport.setDevice('pc');
expect(mockDeviceChange).toHaveBeenCalledTimes(1);
expect(mockViewportChange).toHaveBeenCalledTimes(1);
});
it('setPreview / isPreview / togglePreivew / getViewport / onViewportChange', () => {
const viewport = new Viewport();
const mockViewportChange = jest.fn();
const mockPreivewChange = jest.fn();
const off = viewport.onViewportChange(mockViewportChange);
const offPreview = viewport.onPreview(mockPreivewChange);
viewport.setPreview(true);
expect(viewport.isPreview).toBeTruthy;
expect(viewport.getViewport()).toBe('preview-pc');
expect(mockViewportChange).toHaveBeenCalledTimes(1);
expect(mockPreivewChange).toHaveBeenCalledTimes(1);
viewport.setPreview(false);
expect(viewport.isPreview).toBeFalsy;
expect(viewport.getViewport()).toBe('design-pc');
expect(mockViewportChange).toHaveBeenCalledTimes(2);
expect(mockPreivewChange).toHaveBeenCalledTimes(2);
viewport.togglePreview();
expect(viewport.getViewport()).toBe('preview-pc');
expect(mockViewportChange).toHaveBeenCalledTimes(3);
expect(mockPreivewChange).toHaveBeenCalledTimes(3);
viewport.togglePreview();
expect(viewport.getViewport()).toBe('design-pc');
expect(mockViewportChange).toHaveBeenCalledTimes(4);
expect(mockPreivewChange).toHaveBeenCalledTimes(4);
off();
offPreview();
viewport.togglePreview();
expect(mockViewportChange).toHaveBeenCalledTimes(4);
expect(mockPreivewChange).toHaveBeenCalledTimes(4);
});
it('setFocusTarget / returnFocus / setFocus / isFocus / onFocusChange', () => {
const viewport = new Viewport();
const mockFocusChange = jest.fn();
const off = viewport.onFocusChange(mockFocusChange);
viewport.setFocusTarget(document.createElement('div'));
viewport.returnFocus();
viewport.setFocus(true);
expect(viewport.isFocus()).toBeTruthy();
expect(mockFocusChange).toHaveBeenCalledTimes(1);
expect(mockFocusChange).toHaveBeenLastCalledWith(true);
viewport.setFocus(false);
expect(viewport.isFocus()).toBeFalsy();
expect(mockFocusChange).toHaveBeenCalledTimes(2);
expect(mockFocusChange).toHaveBeenLastCalledWith(false);
off();
viewport.setFocus(false);
expect(mockFocusChange).toHaveBeenCalledTimes(2);
});
it('isFullscreen / toggleFullscreen / setFullscreen / onFullscreenChange', () => {
const viewport = new Viewport();
const mockFullscreenChange = jest.fn();
const off = viewport.onFullscreenChange(mockFullscreenChange);
mockSetFullscreen(false);
viewport.setFullscreen(true);
mockSetFullscreen(true);
expect(viewport.isFullscreen()).toBeTruthy;
// expect(mockFullscreenChange).toHaveBeenCalledTimes(1);
viewport.setFullscreen(true);
// expect(mockFullscreenChange).toHaveBeenCalledTimes(1);
mockSetFullscreen(true);
viewport.setFullscreen(false);
mockSetFullscreen(false);
expect(viewport.isFullscreen()).toBeFalsy;
// expect(mockFullscreenChange).toHaveBeenCalledTimes(2);
viewport.setFullscreen(false);
// expect(mockFullscreenChange).toHaveBeenCalledTimes(2);
mockSetFullscreen(true);
viewport.toggleFullscreen();
mockSetFullscreen(false);
// expect(mockFullscreenChange).toHaveBeenCalledTimes(3);
viewport.toggleFullscreen();
// expect(mockFullscreenChange).toHaveBeenCalledTimes(4);
off();
viewport.toggleFullscreen();
// expect(mockFullscreenChange).toHaveBeenCalledTimes(4);
});
it('setWithShell', () => {
const viewport = new Viewport();
viewport.setWithShell();
});
it('onSlateFixedChange', () => {
const viewport = new Viewport();
const mockSlateFixedChange = jest.fn();
const off = viewport.onSlateFixedChange(mockSlateFixedChange);
viewport.emitter.emit('slatefixed');
expect(mockSlateFixedChange).toHaveBeenCalledTimes(1);
off();
viewport.emitter.emit('slatefixed');
expect(mockSlateFixedChange).toHaveBeenCalledTimes(1);
});
it('setGlobalCSS', () => {
const viewport = new Viewport();
viewport.setGlobalCSS([{
media: '*',
type: 'URL',
content: '//path/to.css',
}, {
media: 'ALL',
type: 'text',
content: 'body {font-size: 50px;}',
}, {
media: '',
type: 'text',
content: 'body {font-size: 50px;}',
}, {
media: 'mobile',
type: 'text',
content: 'body {font-size: 50px;}',
}]);
viewport.cssResourceSet[0].apply();
viewport.cssResourceSet[0].init();
viewport.cssResourceSet[1].apply();
viewport.cssResourceSet[1].apply();
viewport.cssResourceSet[1].unmount();
viewport.setGlobalCSS([{
media: '*',
type: 'URL',
content: '//path/to.css',
}, {
media: 'ALL',
type: 'text',
content: 'body {font-size: 50px;}',
}, {
media: '',
type: 'text',
content: 'body {font-size: 50px;}',
}, {
media: 'mobile',
type: 'text',
content: 'body {font-size: 50px;}',
}]);
});
});

View File

@ -1,3 +0,0 @@
it.skip('should ', () => {
});

View File

@ -0,0 +1,96 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor } from '@ali/lowcode-editor-core';
import {
compatibleReducer,
} from '../../src/props-reducers/downgrade-schema-reducer';
import formSchema from '../fixtures/schema/form';
describe('compatibleReducer 测试', () => {
it('compatibleReducer 测试', () => {
const downgradedProps = {
a: {
type: 'JSBlock',
value: {
componentName: 'Slot',
props: {
slotTitle: '标题',
slotName: 'title',
},
children: [],
},
},
c: {
c1: {
type: 'JSBlock',
value: {
componentName: 'Slot',
props: {
slotTitle: '标题',
slotName: 'title',
},
},
},
},
d: {
type: 'variable',
variable: 'state.a',
value: '111',
},
e: {
e1: {
type: 'variable',
variable: 'state.b',
value: '222',
},
e2: {
type: 'JSExpression',
value: 'state.b',
mock: '222',
events: {},
},
},
};
expect(compatibleReducer({
a: {
type: 'JSSlot',
title: '标题',
name: 'title',
value: [],
},
c: {
c1: {
type: 'JSSlot',
title: '标题',
name: 'title',
value: undefined,
},
},
d: {
type: 'JSExpression',
value: 'state.a',
mock: '111',
},
e: {
e1: {
type: 'JSExpression',
value: 'state.b',
mock: '222',
},
e2: {
type: 'JSExpression',
value: 'state.b',
mock: '222',
events: {},
},
},
})).toEqual(downgradedProps);
});
it('空值', () => {
expect(compatibleReducer(null)).toBeNull;
expect(compatibleReducer(undefined)).toBeUndefined;
expect(compatibleReducer(111)).toBe(111);
});
});

View File

@ -0,0 +1,81 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor } from '@ali/lowcode-editor-core';
import { filterReducer } from '../../src/props-reducers/filter-reducer';
import formSchema from '../fixtures/schema/form';
describe('filterReducer 测试', () => {
it('filterReducer 测试 - 有 filters', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
filters: [
{
name: 'shouldBeFitlered',
filter: () => false,
},
{
name: 'keeped',
filter: () => true,
},
{
name: 'throwErr',
filter: () => { throw new Error('xxx'); },
},
{
name: 'zzz',
filter: () => true,
},
],
},
};
},
},
settingEntry: {
getProp(propName) {
return { name: propName };
},
},
};
expect(filterReducer({
shouldBeFitlered: 111,
keeped: 222,
noCorresponingFilter: 222,
throwErr: 111,
}, mockNode)).toEqual({
keeped: 222,
noCorresponingFilter: 222,
throwErr: 111,
});
});
it('filterReducer 测试 - 无 filters', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
filters: [],
},
};
},
},
settingEntry: {
getProp(propName) {
return { name: propName };
},
},
};
expect(filterReducer({
shouldBeFitlered: 111,
keeped: 222,
noCorresponingFilter: 222,
}, mockNode)).toEqual({
shouldBeFitlered: 111,
keeped: 222,
noCorresponingFilter: 222,
});
});
});

View File

@ -0,0 +1,488 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { initNodeReducer } from '../../src/props-reducers/init-node-reducer';
import formSchema from '../fixtures/schema/form';
describe('initNodeReducer 测试', () => {
it('initNodeReducer 测试 - 有 initials', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [
{
name: 'propA',
initial: () => '111',
},
{
name: 'propB',
initial: () => '111',
},
{
name: 'propC',
initial: () => {
throw new Error('111');
},
},
{
name: 'propD',
initial: () => '111',
},
{
name: 'propE',
initial: () => '111',
},
{
name: 'propF',
initial: () => '111',
},
],
},
};
},
prototype: {
options: {
configure: [
{
name: 'propF',
setter: {
type: {
displayName: 'I18nSetter',
},
},
},
],
},
},
},
settingEntry: {
getProp(propName) {
return { name: propName };
},
},
props: {
has() {
return false;
},
add() {},
},
};
expect(
initNodeReducer(
{
propA: '111',
propC: '222',
propD: {
type: 'JSExpression',
mock: '111',
},
propE: {
type: 'variable',
value: '111',
},
},
mockNode,
),
).toEqual({
propA: '111',
propB: '111',
propC: '222',
propD: {
type: 'JSExpression',
mock: '111',
},
propE: {
type: 'variable',
value: '111',
},
propF: {
type: 'i18n',
use: 'zh_CN',
zh_CN: '111',
},
});
});
it('filterReducer 测试 - 无 initials', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {},
};
},
},
settingEntry: {
getProp(propName) {
return { name: propName };
},
},
};
expect(
initNodeReducer(
{
propA: 111,
},
mockNode,
),
).toEqual({
propA: 111,
});
});
describe('i18n', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [
{
name: 'propF',
initial: () => 111,
},
],
},
};
},
},
prototype: {
options: {
configure: [
{
name: 'propF',
setter: {
type: {
displayName: 'I18nSetter',
},
},
},
],
},
},
props: {
has() {
return false;
},
add() {},
},
};
it('isI18NObject(ov): true', () => {
expect(
initNodeReducer(
{
propF: {
type: 'i18n',
zh_CN: '222',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'i18n',
zh_CN: '222',
},
});
});
it('isJSExpression(ov): true', () => {
expect(
initNodeReducer(
{
propF: {
type: 'JSExpression',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'JSExpression',
value: 'state.a',
},
});
});
it('isJSBlock(ov): true', () => {
expect(
initNodeReducer(
{
propF: {
type: 'JSBlock',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'JSBlock',
value: 'state.a',
},
});
});
it('isJSSlot(ov): true', () => {
expect(
initNodeReducer(
{
propF: {
type: 'JSSlot',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'JSSlot',
value: 'state.a',
},
});
});
it('isVariable(ov): true', () => {
expect(
initNodeReducer(
{
propF: {
type: 'variable',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'variable',
value: 'state.a',
},
});
});
it('isI18NObject(v): false', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [
{
name: 'propF',
initial: () => 111,
},
],
},
};
},
prototype: {
options: {
configure: [
{
name: 'propF',
setter: {
type: {
displayName: 'I18nSetter',
},
},
},
],
},
},
},
props: {
has() {
return false;
},
add() {},
},
};
expect(
initNodeReducer(
{
propF: {
type: 'variable',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'variable',
value: 'state.a',
},
});
});
it('isI18NObject(v): false', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [{
name: 'propF',
initial: () => 111,
}],
},
};
},
},
prototype: {
options: {
configure: [
{
name: 'propF',
setter: {
type: {
displayName: 'I18nSetter',
},
},
},
],
},
},
props: {
has() {
return false;
},
add() {},
},
};
expect(
initNodeReducer(
{
propF: {
type: 'variable',
value: 'state.a',
},
},
mockNode,
),
).toEqual({
propF: {
type: 'variable',
value: 'state.a',
},
});
});
});
it('成功使用兼容后的 i18n 对象', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [{
name: 'propF',
initial: () => {
return {
type: 'i18n',
use: 'zh_CN',
zh_CN: '111',
};
},
}],
},
};
},
prototype: {
options: {
configure: [
{
name: 'propF',
setter: {
type: {
displayName: 'I18nSetter',
},
},
},
],
},
},
},
props: {
has() {
return false;
},
add() {},
},
};
expect(
initNodeReducer(
{
propF: '111',
},
mockNode,
),
).toEqual({
propF: {
type: 'i18n',
use: 'zh_CN',
zh_CN: '111',
},
});
});
describe('fieldId', () => {
const mockNode = {
componentMeta: {
getMetadata() {
return {
experimental: {
initials: [
{
name: 'propA',
initial: () => '111',
},
],
},
};
},
},
settingEntry: {
getProp(propName) {
return { name: propName };
},
},
props: {
has() {
return false;
},
add() {},
},
};
const editor = new Editor();
globalContext.register(editor, Editor);
const designer = new Designer({ editor });
editor.set('designer', designer);
designer.project.open(formSchema);
it('fieldId - 已存在', () => {
expect(initNodeReducer({
propA: '111',
fieldId: 'form',
}, mockNode)).toEqual({
propA: '111',
fieldId: undefined,
});
});
it('fieldId - 已存在,但有全局关闭标识', () => {
window.__disable_unique_id_checker__ = true;
expect(initNodeReducer({
propA: '111',
fieldId: 'form',
}, mockNode)).toEqual({
propA: '111',
fieldId: 'form',
});
});
});
});

View File

@ -0,0 +1,78 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { liveLifecycleReducer } from '../../src/props-reducers/live-lifecycle-reducer';
import formSchema from '../fixtures/schema/form';
const editor = new Editor();
globalContext.register(editor, Editor);
it('liveLifecycleReducer 测试 - live', () => {
const mockDidMount = jest.fn();
const mockWillUnmount = jest.fn();
editor.set('designMode', 'live');
const newProps = liveLifecycleReducer(
{
lifeCycles: {
didMount: mockDidMount,
willUnmount: mockWillUnmount,
},
},
{
isRoot() {
return true;
},
},
);
const { lifeCycles } = newProps;
expect(typeof lifeCycles.componentDidMount).toBe('function');
expect(typeof lifeCycles.componentWillUnMount).toBe('function');
lifeCycles.didMount();
lifeCycles.willUnmount();
expect(mockDidMount).toHaveBeenCalled();
expect(mockWillUnmount).toHaveBeenCalled();
});
it('liveLifecycleReducer 测试 - design', () => {
const mockDidMount = jest.fn();
const mockWillUnmount = jest.fn();
editor.set('designMode', 'design');
const newProps = liveLifecycleReducer(
{
lifeCycles: {
didMount: mockDidMount,
willUnmount: mockWillUnmount,
},
},
{
isRoot() {
return true;
},
},
);
const { lifeCycles } = newProps;
expect(lifeCycles).toEqual({});
});
it('liveLifecycleReducer 测试', () => {
const mockDidMount = jest.fn();
const mockWillUnmount = jest.fn();
editor.set('designMode', 'design');
const newProps = liveLifecycleReducer(
{
propA: '111',
},
{
isRoot() {
return true;
},
},
);
const { lifeCycles } = newProps;
expect(lifeCycles).toBeUndefined;
});

View File

@ -0,0 +1,28 @@
import '../fixtures/window';
import { nodeTopFixedReducer } from '../../src/props-reducers/node-top-fixed-reducer';
import formSchema from '../fixtures/schema/form';
it('nodeTopFixedReducer 测试', () => {
expect(
nodeTopFixedReducer(
{
propA: '111',
},
{ componentMeta: { isTopFixed: true } },
),
).toEqual({
propA: '111',
__isTopFixed__: true,
});
expect(
nodeTopFixedReducer(
{
propA: '111',
},
{ componentMeta: { } },
),
).toEqual({
propA: '111',
});
});

View File

@ -0,0 +1,62 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { removeEmptyPropsReducer } from '../../src/props-reducers/remove-empty-prop-reducer';
import formSchema from '../fixtures/schema/form';
it('removeEmptyPropsReducer 测试', () => {
const newProps = removeEmptyPropsReducer(
{
propA: '111',
dataSource: {
online: [
{
options: {
params: [
{
name: 'propA',
value: '111',
},
{
value: '111',
},
],
},
},
],
},
},
{
isRoot() {
return true;
},
},
);
expect(newProps).toEqual({
propA: '111',
dataSource: {
online: [
{
options: {
params: [{
name: 'propA',
value: '111',
}, {
value: '111',
}],
},
},
],
list: [
{
options: {
params: {
propA: '111',
},
},
},
],
},
});
});

View File

@ -0,0 +1,121 @@
import '../fixtures/window';
import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import { stylePropsReducer } from '../../src/props-reducers/style-reducer';
import formSchema from '../fixtures/schema/form';
const editor: Editor = new Editor();
globalContext.register(editor, Editor);
beforeEach(() => {
// const designer = new Designer({ editor });
editor.set('designer', {
currentDocument: {
simulator: {
contentDocument: document,
},
},
});
});
// designer.project.open(formSchema);
describe('stylePropsReducer 测试', () => {
it('无 style 相关属性', () => {
expect(stylePropsReducer({ propA: 1 })).toEqual({ propA: 1 });
});
it('__style__', () => {
const props = {
__style__: {
'font-size': '50px',
},
};
const mockNode = { id: 'id1' };
expect(stylePropsReducer(props, mockNode)).toEqual({
className: '_css_pesudo_id1',
__style__: {
'font-size': '50px',
},
});
expect(document.querySelector('#_style_pesudo_id1')).textContent =
'._css_pesudo_id1 { font-size: 50px; }';
});
it('__style__ - 无 contentDocument', () => {
editor.set('designer', {
currentDocument: {
simulator: {
contentDocument: undefined,
},
},
});
const props = {
__style__: {
'font-size': '50px',
},
};
const mockNode = { id: 'id11' };
expect(stylePropsReducer(props, mockNode)).toEqual({
__style__: {
'font-size': '50px',
},
});
expect(document.querySelector('#_style_pesudo_id11')).toBeNull;
});
it('__style__ - css id 已存在', () => {
const s = document.createElement('style');
s.setAttribute('type', 'text/css');
s.setAttribute('id', '_style_pesudo_id2');
document.getElementsByTagName('head')[0].appendChild(s);
s.appendChild(document.createTextNode('body {}'));
const props = {
__style__: {
'font-size': '50px',
},
};
const mockNode = { id: 'id2' };
expect(stylePropsReducer(props, mockNode)).toEqual({
className: '_css_pesudo_id2',
__style__: {
'font-size': '50px',
},
});
expect(document.querySelector('#_style_pesudo_id2')).textContent =
'._css_pesudo_id2 { font-size: 50px; }';
});
it('containerStyle', () => {
const props = {
containerStyle: {
'font-size': '50px',
},
};
const mockNode = { id: 'id3' };
expect(stylePropsReducer(props, mockNode)).toEqual({
className: '_css_pesudo_id3',
containerStyle: {
'font-size': '50px',
},
});
expect(document.querySelector('#_style_pesudo_id3')).textContent =
'._css_pesudo_id3 { font-size: 50px; }';
});
it('pageStyle', () => {
const props = {
pageStyle: {
'font-size': '50rpx',
},
};
const mockNode = { id: 'id4' };
expect(stylePropsReducer(props, mockNode)).toEqual({
className: 'engine-document',
pageStyle: {
'font-size': '50rpx',
},
});
expect(document.querySelector('#_style_pesudo_id4')).textContent =
'._css_pesudo_id4 { font-size: 50px; }';
});
});

View File

@ -13,5 +13,6 @@ module.exports = {
'no-confusing-arrow': 1,
'no-case-declarations': 1,
'lines-between-class-members': 0,
'@typescript-eslint/member-ordering': 0,
}
}

View File

@ -66,4 +66,12 @@ export default class Area<C extends IWidgetBaseConfig = any, T extends IWidget =
show() {
this.setVisible(true);
}
// ========== compatible for vision ========
/**
* @deprecated
*/
removeAction(config: string): number {
return this.remove(config);
}
}

View File

@ -350,6 +350,8 @@ export class Skeleton {
return this.leftFloatArea.add(parsedConfig);
case 'stages':
return this.stages.add(parsedConfig);
default:
// do nothing
}
}
}